Merge from 'brandonscript/usergrid-nodejs'
diff --git a/.gitignore b/.gitignore
index c564040..c727bb7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
+node_modules
 .idea
-node_modules/
\ No newline at end of file
+.DS_Store
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..06e77f9
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,12 @@
+language: node_js
+node_js:
+- '5.1.0'
+install: 
+- 'npm install'
+- 'npm -g install mocha'
+script: 
+- 'mocha tests --bail --target=2.1'
+notifications:
+  email:
+    on_failure: change
+    on_success: change
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 1c1b5ee..d955a86 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,326 +1,11 @@
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
 
-Apache Usergrid itself is licensed under the terms of the Apache License:
+    http://www.apache.org/licenses/LICENSE-2.0
 
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-------------------------------------------------------------------------------
-
-USERGRID SUBCOMPONENTS
-
-The Usergrid software includes a number of subcomponents with separate
-copyrights and license terms. Your use of the source code for these 
-subcomponents is subject to the terms and conditions of the following 
-licenses. 
-
-IOS SDK
--------
-For the SBJson component:
- 
- Copyright (c) Stig Brautaset. All rights reserved.
- 
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
- 
- * Redistributions of source code must retain the above copyright notice, this
-   list of conditions and the following disclaimer.
- 
- * Redistributions in binary form must reproduce the above copyright notice,
-   this list of conditions and the following disclaimer in the documentation
-   and/or other materials provided with the distribution.
- 
- * Neither the name of the author nor the names of its contributors may be used
-   to endorse or promote products derived from this software without specific
-   prior written permission.
- 
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-For the SSKeychain component:
------------------------------
-
- Copyright (c) Sam Soffes, http://soff.es
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be
- included in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-Other components:
------------------
-
-This product bundles angular.js
-Copyright(c) Google, Inc. Released under the MIT license.
-
-This product bundles angular-scenario.js, part of jQuery JavaScript Library
-which Includes Sizzle.js Copyright (c) jQuery Foundation, Inc. and others.
-Released under the MIT license.
-
-This product bundles Bootstrap Copyright (c) Twitter, Inc
-Licensed under the MIT license.
-
-The product bundles Intro.js (MIT licensed)
-Copyright (c) usabli.ca - A weekend project by Afshin Mehrabani (@afshinmeh)
-
-This product bundles jQuery
-Licensed under MIT license.
-
-This product bundles jQuery-UI
-Licensed under MIT license.
-
-This product bundles jQuery Sparklines (New BSD License)
-Copyright (c) Splunk Inc.
-
-This product bundles Mocha.
-All rights reserved. Licensed under MIT.
-Copyright (c) TJ Holowaychuk <tj@vision-media.ca>
-
-This product bundles NewtonSoft.Json under MIT license
-
-This product bundles NPM MD5 (BSD-3 licensed)
-Copyright (c) Paul Vorbach and Copyright (C), Jeff Mott.
-
-This product bundles NSubsttute under BSD license
-
-This product bundles SBJson, which is available under a "3-clause BSD" license.
-For details, see sdks/ios/UGAPI/SBJson/ .
-
-This product bundles Sphinx under BSD license
-
-This product bundles SSKeychain, which is available under a "MIT/X11" license.
-For details, see sdks/ios/UGAPI/SSKeychain/.
-
-This product bundles SSToolkit.
-Copyright (c) Sam Soffes. All rights reserved.
-These files can be located within the /sdks/ios package.
-
-This product bundles Entypo, CC by SA license
-
-This product bundles date.min.js, MIT license
-
-This product bundles jquery.ui.timepicker.min.js, MIT license
-
-This product bundles blanket_mocha.min.js, MIT license
-
-This product bundles FontAwesome, SIL Open Font License
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..65f8329
--- /dev/null
+++ b/README.md
@@ -0,0 +1,778 @@
+[![Codacy Badge](https://api.codacy.com/project/badge/grade/034bc34302b646bf932c7c0307e0e313)](https://www.codacy.com/app/brandonscript/usergrid-nodejs)
+[![Travis CI Badge](https://travis-ci.org/brandonscript/usergrid-nodejs.svg?branch=master)](https://travis-ci.org/brandonscript/usergrid-nodejs)
+[![npm version](https://badge.fury.io/js/usergrid.svg)](https://badge.fury.io/js/usergrid)
+
+# usergrid-nodejs
+Node.js SDK 2.0 for Usergrid 
+
+Version 2.0 of this SDK is currently a work in progress; documentation and implementation are subject to change.
+
+_**Note:** This Node.js SDK 2.0 for Usergrid is **not** backwards compatible with 0.1X versions of the SDK. If your application is dependent on the 0.1X set of Node.js APIs, you will need to continue using the 0.1X version (see below for installation instructions)._
+
+## Current Release
+
+- Pre-release: [2.0 Release Candidate 2](https://github.com/brandonscript/usergrid-nodejs/releases), available here or on npm
+- Stable: [0.10.11](https://github.com/apache/usergrid/tree/master/sdks/nodejs)
+ 
+## 2.X Bugs
+
+Please open an [issue](https://github.com/brandonscript/usergrid-nodejs/issues/new)
+
+## Known Issues
+
+- Native support for push notifications is slated for RC3. Workaround is to send a regular POST request to `'devices/<device_ID>/notifications'`
+- There is no clean way to require submodules (e.g. `UsergridClient` or `UsergridEntity`) modules without referencing the full path to `../lib/<class>`.
+- Any other functionality that is missing or expected, please open an issue.
+
+## Installation
+
+To install the latest **stable** 0.1X build:
+
+    npm install usergrid
+
+(Or add `"usergrid": "~0.10.11"` to your package.json)
+
+To install the 2.0 release candidates, install from [npm](https://www.npmjs.com/package/usergrid), specifying the version `~2.0.0-rc`:
+
+    npm install usergrid@~2.0.0-rc
+
+(Or add `"usergrid": "~2.0.0-rc"` to your package.json)
+
+If you want access to the latest development build (you will need to run `npm install` to keep it up to date):
+
+    npm install brandonscript/usergrid-nodejs
+
+## Usage
+
+_**Note:** This section is a work in progress. In its current release candidate state, this SDK is only recommended for developers familiar with Usergrid, Node.js, and ideally Mocha tests. It is not recommended for production applications. For additional advanced/comprehensive usage, see `/tests`._
+
+The Usergrid Node.js SDK is built on top of [request](https://github.com/request/request). As such, it behaves almost as a drop-in replacement. Where you would expect a standard error-first callback from request, the same is true of the Usergrid SDK methods. Where you would expect a response object as the second parameter in the callback, the same is true for the Usergrid SDK.
+
+
+### Initialization
+
+There are two different ways of initializing the Usergrid Node.js SDK: 
+
+1. The singleton pattern is both convenient and enables the developer to use a globally available and always-initialized shared instance of Usergrid. 
+
+	```js
+	var Usergrid = require('usergrid')
+	Usergrid.init({
+	    orgId: '<org-id>',
+	    appId: '<app-id>'
+	})
+	    
+	// or you can load from a config file; see config.sample.json
+	    
+	var Usergrid = require('usergrid')
+	Usergrid.init() // defaults to use config.json
+	```
+**Config File:**  Optionally, you can use a config file to provide the usergrid credentials for your app.  The usergrid module crawls your App file structure to find files named `usergrid.json` or a `config.json`.  If there are multiple files with one of these names present at different locations under the app, only one of them will be used and the others are ignored.  This may cause use of an unintended backend.  Please make sure you have only one of these files present in the root and subdirectories of your app.
+
+2. The instance pattern enables the developer to manage instances of the Usergrid client independently and in an isolated fashion. The primary use-case for this is when an application connects to multiple Usergrid targets.
+
+	```js
+	var UsergridClient = require('./node_modules/usergrid/lib/client')
+	var client = new UsergridClient(config)
+	```
+
+_**Note:** Examples in this readme assume you are using the `Usergrid` shared instance. If you've implemented the instance pattern instead, simply replace `Usergrid` with your client instance variable. See `/tests` for additional examples._
+
+## RESTful operations
+
+When making any RESTful call, a `type` parameter (or `path`) is always required. Whether you specify this as an argument, in an object as a parameter, or as part of a `UsergridQuery` object is up to you.
+
+### GET()
+
+To get entities in a collection:
+
+```js
+Usergrid.GET('collection', function(error, usergridResponse, entities) {
+    // entities is an array of UsergridEntity objects
+})
+```
+    
+To get a specific entity in a collection by uuid or name:
+
+```js
+Usergrid.GET('collection', '<uuid-or-name>', function(error, usergridResponse, entity) {
+    // entity, if found, is a UsergridEntity object
+})
+```
+    
+To get specific entities in a collection by passing a UsergridQuery object:
+
+```js
+var query = new UsergridQuery('cats')
+                             .gt('weight', 2.4)
+                             .contains('color', 'bl*')
+                             .not
+                             .eq('color', 'blue')
+                             .or
+                             .eq('color', 'orange')
+                             
+// this will build out the following query:
+// select * where weight > 2.4 and color contains 'bl*' and not color = 'blue' or color = 'orange'
+    
+Usergrid.GET(query, function(error, usergridResponse) {
+    // entities is an array of UsergridEntity objects matching the specified query
+})
+```
+    
+### POST() and PUT()
+
+POST and PUT requests both require a JSON body payload. You can pass either a standard JavaScript object or a `UsergridEntity` instance. While the former works in principle, best practise is to use a `UsergridEntity` wherever practical. When an entity has a uuid or name property and already exists on the server, use a PUT request to update it. If it does not, use POST to create it.
+
+To create a new entity in a collection (POST):
+
+```js
+var entity = new UsergridEntity({
+    type: 'restaurant',
+    restaurant: 'Dino's Deep Dish,
+    cuisine: 'pizza'
+})
+    
+// or
+    
+var entity = {
+    type: 'restaurant',
+    restaurant: 'Dino's Deep Dish,
+    cuisine: 'pizza'
+}
+    
+Usergrid.POST(entity, function(error, usergridResponse, entity) {
+    // entity should now have a uuid property and be created
+})
+    
+// you can also POST an array of entities:
+
+var entities = [
+    new UsergridEntity({
+        type: 'restaurant',
+        restaurant: 'Dino's Deep Dish,
+        cuisine: 'pizza'
+    }), 
+    new UsergridEntity({
+        type: 'restaurant',
+        restaurant: 'Pizza da Napoli',
+        cuisine: 'pizza'
+    })
+]
+    
+Usergrid.POST(entities, function(error, usergridResponse, entities) {
+    //
+})
+```
+    
+To update an entity in a collection (PUT request):
+
+```js
+var entity = new UsergridEntity({
+    type: 'restaurant',
+    restaurant: 'Pizza da Napoli',
+    cuisine: 'pizza'
+})
+    
+Usergrid.POST(entity, function(error, usergridResponse, entity) {
+    entity.owner = 'Mia Carrara'
+    Usergrid.PUT(entity, function(error, usergridResponse, entity) {
+        // entity now has the property 'owner'
+    })
+})
+    
+// or update a set of entities by passing a UsergridQuery object
+    
+var query = new UsergridQuery('restaurants')
+                             .eq('cuisine', 'italian')
+                             
+// this will build out the following query:
+// select * where cuisine = 'italian'
+    
+Usergrid.PUT(query, { keywords: ['pasta'] }, function(error, usergridResponse) {
+    /* the first 10 entities matching this query criteria will be updated:
+       e.g.:
+       [
+           {
+               "type": "restaurant",
+               "restaurant": "Il Tarazzo",
+               "cuisine": "italian",
+               "keywords": [
+                   "pasta"
+               ]
+           },
+           {
+               "type": "restaurant",
+               "restaurant": "Cono Sur Pizza & Pasta",
+               "cuisine": "italian",
+               "keywords": [
+                   "pasta"
+               ]
+           }
+        ]
+    */
+})
+```
+    
+### DELETE()
+
+DELETE requests require either a specific entity or a `UsergridQuery` object to be passed as an argument.
+    
+To delete a specific entity in a collection by uuid or name:
+
+```js
+Usergrid.DELETE('collection', '<uuid-or-name>', function(error, usergridResponse) {
+    // if successful, entity will now be deleted
+})
+```
+    
+To specific entities in a collection by passing a `UsergridQuery` object:
+
+```js
+var query = new UsergridQuery('cats')
+                             .eq('color', 'black')
+                             .or
+                             .eq('color', 'white')
+                             
+// this will build out the following query:
+// select * where color = 'black' or color = 'white'
+    
+Usergrid.DELETE(query, function(error, usergridResponse) {
+    // the first 10 entities matching this query criteria will be deleted
+})
+```
+
+## Entity operations and convenience methods
+
+`UsergridEntity` has a number of helper/convenience methods to make working with entities more convenient. If you are _not_ utilizing the `Usergrid` shared instance, you must pass an instance of `UsergridClient` as the first argument to any of these helper methods.
+
+### reload()
+
+Reloads the entity from the server
+
+```js
+entity.reload(function(error, usergridResponse) {
+    // entity is now reloaded from the server
+})
+```
+    
+### save()
+
+Saves (or creates) the entity on the server
+
+```js
+entity.aNewProperty = 'A new value'
+entity.save(function(error, usergridResponse) {
+    // entity is now updated on the server
+})
+```
+    
+### remove()
+
+Deletes the entity from the server
+
+```js
+entity.remove(function(error, usergridResponse) {
+    // entity is now deleted on the server and the local instance should be destroyed
+})
+```
+    
+## Authentication, current user, and authMode
+
+### appAuth and authenticateApp()
+
+`Usergrid` can use the app client ID and secret that were passed upon initialization and automatically retrieve an app-level token for these credentials.
+
+```js
+Usergrid.setAppAuth('<client-id>', '<client-secret>')
+Usergrid.authenticateApp(function(error, usergridResponse, token) {
+    // Usergrid.appAuth is created automatically when this call is successful
+})
+```
+
+### currentUser and authenticateUser()
+
+`Usergrid` has a special `currentUser` property. By default, when calling `authenticateUser()`, `.currentUser` will be set to this user if the authentication flow is successful.
+
+```js
+Usergrid.authenticateUser({
+    username: '<username>',
+    password: '<password>'
+}, function(error, usergridResponse, token) {
+    // Usergrid.currentUser is set to the authenticated user and the token is stored within that context
+})
+```
+    
+If you want to utilize authenticateUser without setting as the current user, simply pass a `false` boolean value as the second parameter:
+
+```js
+Usergrid.authenticateUser({
+    username: '<username>',
+    password: '<password>'
+}, false, function(error, usergridResponse, token) {
+    
+})
+```
+
+### authMode
+
+Auth-mode is used to determine what the `UsergridClient` will use for authorization.
+
+By default, `Usergrid.authMode` is set to `UsergridAuth.AUTH_MODE_USER`, whereby if a non-expired `UsergridUserAuth` exists in `UsergridClient.currentUser`, this token is used to authenticate all API calls.
+
+If instead `Usergrid.authMode` is set to `UsergridAuth.AUTH_MODE_NONE`, all API calls will be performed unauthenticated. 
+
+If instead `Usergrid.authMode` is set to `UsergridAuth.AUTH_MODE_APP`, all API calls will be performed using the client credentials token, _if_ they're available (i.e. `authenticateApp()` was performed at some point). 
+
+### usingAuth()
+
+At times it is desireable to have complete, granular control over the authentication context of an API call. To facilitate this, the passthrough function `.usingAuth()` allows you to pre-define the auth context of the next API call.
+
+```js
+// assume Usergrid.authMode = UsergridAuth.AUTH_MODE_NONE
+    
+Usergrid.usingAuth(Usergrid.appAuth).POST('roles/guest/permissions', {
+    permission: "get,post,put,delete:/**"
+}, function(error, usergridResponse) {
+    // here we've temporarily used the client credentials to modify permissions
+    // subsequent calls will not use this auth context
+})
+```
+    
+## User operations and convenience methods
+
+`UsergridUser` has a number of helper/convenience methods to make working with user entities more convenient. If you are _not_ utilizing the `Usergrid` shared instance, you must pass an instance of `UsergridClient` as the first argument to any of these helper methods.
+    
+### create()
+
+Creating a new user:
+
+```js
+var user = new UsergridUser({
+    username: 'username',
+    password: 'password'
+})
+    
+user.create(function(error, usergridResponse, user) {
+    // user has now been created and should have a valid uuid
+})
+```
+    
+### login()
+
+A simpler means of retrieving a user-level token:
+
+```js
+var user = new UsergridUser({
+    username: 'username',
+    password: 'password'
+})
+    
+user.login(function(error, usergridResponse, token) {
+    // user is now logged in
+})
+```
+
+### logout()
+
+Logs out the selected user. You can also use this convenience method on `Usergrid.currentUser`.
+
+```js
+user.logout(function(error, usergridResponse) {
+    // user is now logged out
+})
+```
+    
+### logoutAllSessions()
+
+Logs out all sessions for the selected user and destroys all active tokens. You can also use this convenience method on `Usergrid.currentUser`.
+
+```js
+user.logoutAllSessions(function(error, usergridResponse) {
+    // user is now logged out from everywhere
+})
+```
+    
+### resetPassword()
+
+Resets the password for the selected user.
+
+```js
+user.resetPassword({
+    oldPassword: '2cool4u',
+    newPassword: 'correct-horse-battery-staple',
+}, function(error, response, success) {
+    // if it was done correctly, the new password will be changed
+    // 'success' is a boolean value that indicates whether it was changed successfully
+})
+```
+    
+### UsergridUser.CheckAvailable()
+
+This is a class (static) method that allows you to check whether a username or email address is available or not.
+
+```js
+UsergridUser.CheckAvailable(client, {
+    email: 'email'
+}, function(err, response, exists) {
+   // 'exists' is a boolean value that indicates whether a user already exists
+})
+    
+UsergridUser.CheckAvailable(client, {
+    username: 'username'
+}, function(err, response, exists) {
+   
+})
+    
+UsergridUser.CheckAvailable(client, {
+    email: 'email',
+    username: 'username', // checks both email and username
+}, function(err, response, exists) {
+    // 'exists' returns true if either username or email exist
+})
+```
+    
+## Querying and filtering data
+
+### UsergridQuery initialization
+
+The `UsergridQuery` class allows you to build out complex query filters using the Usergrid [query syntax](http://docs.apigee.com/app-services/content/querying-your-data).
+
+The first parameter of the `UsergridQuery` builder pattern should be the collection (or type) you intend to query. You can either pass this as an argument, or as the first builder object:
+
+```js
+var query = new UsergridQuery('cats')
+// or
+var query = new UsergridQuery().type('cats')
+var query = new UsergridQuery().collection('cats')
+```
+
+You then can layer on additional queries:
+
+```js
+var query = new UsergridQuery('cats')
+            .gt('weight', 2.4)
+            .contains('color', 'bl*')
+            .not
+            .eq('color', 'white')
+            .or
+            .eq('color', 'orange') 
+```
+            
+You can also adjust the number of results returned:
+
+```js
+var query = new UsergridQuery('cats').eq('color', 'black').limit(100)
+// returns a maximum of 100 entiteis
+```
+    
+And sort the results:
+
+```js
+var query = new UsergridQuery('cats').eq('color', 'black').asc('name')
+// sorts by 'name', ascending
+```
+    
+And you can do geo-location queries:
+
+```js
+var query = new UsergridQuery('devices').locationWithin(<distanceInMeters>, <latitude>, <longitude>)
+```
+    
+### Using a query in a request
+
+Queries can be passed as parameters to GET, PUT, and DELETE requests:
+
+```js
+Usergrid.GET(query, function(error, usergridResponse, entities) {
+    //
+})
+    
+Usergrid.PUT(query, { aNewProperty: "A new value" }, function(error, usergridResponse, entities) {
+    //
+})
+    
+Usergrid.DELETE(query, function(error, usergridResponse, entities) {
+    //
+})
+```
+    
+While not a typical use case, sometimes it is useful to be able to create a query that works on multiple collections. Therefore, in each one of these RESTful calls, you can optionally pass a 'type' string as the first argument:
+
+```js
+Usergrid.GET('cats', query, function(error, usergridResponse, entities) {
+    //
+})
+```
+    
+### List of query builder objects
+
+`type('string')`
+
+> The collection name to query
+
+`collection('string')`
+
+> An alias for `type`
+
+`eq('key', 'value')` or `equal('key', 'value')`
+
+> Equal to (e.g. `where color = 'black'`)
+
+`contains('key', 'value')`
+
+> Contains a string (e.g.` where color contains 'bl*'`)
+
+`gt('key', 'value')` or `greaterThan('key', 'value')`
+
+> Greater than (e.g. `where weight > 2.4`)
+
+`gte('key', 'value')` or `greaterThanOrEqual('key', 'value')`
+
+> Greater than or equal to (e.g. `where weight >= 2.4`)
+
+`lt('key', 'value')` or `lessThan('key', 'value')`
+
+> Less than (e.g. `where weight < 2.4`)
+
+`lte('key', 'value')` or `lessThanOrEqual('key', 'value')`
+
+> Less than or equal to (e.g. `where weight <= 2.4`)
+
+`not`
+
+> Negates the next block in the builder pattern, e.g.:
+
+```js
+var query = new UsergridQuery('cats').not.eq('color', 'black')
+// select * from cats where not color = 'black'
+```
+
+`and`
+
+> Joins two queries by requiring both of them. `and` is also implied when joining two queries _without_ an operator. E.g.:
+
+```js
+var query = new UsergridQuery('cats').eq('color', 'black').eq('fur', 'longHair')
+// is identical to:
+var query = new UsergridQuery('cats').eq('color', 'black').and.eq('fur', 'longHair')  
+```
+
+`or`
+
+> Joins two queries by requiring only one of them. `or` is never implied. E.g.:
+
+```js
+var query = new UsergridQuery('cats').eq('color', 'black').or.eq('color', 'white')
+```
+    
+> When using `or` and `and` operators, `and` joins will take precedence over `or` joins. You can read more about query operators and precedence [here](http://docs.apigee.com/api-baas/content/supported-query-operators-data-types).
+
+`locationWithin(distanceInMeters, latitude, longitude)`
+
+> Returns entities which have a location within the specified radius. Arguments can be `float` or `int`.
+
+`asc('key')`
+
+> Sorts the results by the specified property, ascending
+
+`desc('key')`
+
+> Sorts the results by the specified property, descending
+
+`sort('key', 'order')`
+
+> Sorts the results by the specified property, in the specified order (`asc` or `desc`).
+ 
+`limit(int)`
+
+> The maximum number of entities to return
+
+`cursor('string')`
+
+> A pagination cursor string
+
+`fromString('query string')`
+
+> A special builder property that allows you to input a pre-defined query string. All other builder properties will be ignored when this property is defined. For example:
+    
+```js
+var query = new UsergridQuery().fromString("select * where color = 'black' order by name asc")
+```
+
+## UsergridResponse object
+
+`UsergridResponse` implements several Usergrid-specific enhancements to [request](https://github.com/request/request). Notably:
+
+### ok
+
+You can check `usergridResponse.ok`, a `bool` value, to see if the response was successful. Any status code < 400 returns true.
+
+```js
+Usergrid.GET('collection', function(error, usergridResponse, entities) {
+    if (usergridResponse.ok) {
+        // woo!
+    }
+})
+```
+    
+### entity, entities, user, users, first, last
+
+Depending on the call you make, you will receive either an array of UsergridEntity objects, or a single entity as the third parameter in the callback. If you're querying the `users` collection, these will also be `UsergridUser` objects, a subclass of `UsergridEntity`.
+
+- `.first` returns the first entity in an array of entities; `.entity` is an alias to `.first`. If there are no entities, both of these will be undefined.
+- `.last` returns the last entity in an array of entities; if there is only one entity in the array, this will be the same as `.first` _and_ `.entity`, and will be undefined if there are no entities in the response.
+- `.entities` will either be an array of entities in the response, or an empty array.
+- `.user` is a special alias for `.entity` for when querying the `users` collection. Instead of being a `UsergridEntity`, it will be its subclass, `UsergridUser`.
+- `.users` is the same as `.user`, though behaves as `.entities` does by returning either an array of UsergridUser objects or an empty array.
+
+Examples:
+
+```js
+Usergrid.GET('collection', function(error, usergridResponse, entities) {
+    // third param is an array of entities because no specific entity was referenced
+    // you can also access:
+    //     usergridResponse.entities
+    //     usergridResponse.first    
+    //     usergridResponse.entity (the first entity)      
+    //     usergridResponse.last		 
+})
+    
+Usergrid.GET('collection', '<uuid or name>', function(error, usergridResponse, entity) {
+    // third param is a single entity object
+    // you can also access:
+    //     usergridResponse.entity
+    //     usergridResponse.first  
+    //     usergridResponse.last                
+})
+    
+Usergrid.GET('users', function(error, usergridResponse, users) {
+    // third param is an array of users because no specific user was referenced
+    // you can also access:
+    //     usergridResponse.users
+    //     usergridResponse.user (the first user)          
+    //     usergridResponse.last 
+})
+    
+Usergrid.GET('users', '<uuid, username, or email>', function(error, usergridResponse, user) {
+    // third param is a single user object
+    // you can also access:
+    //     usergridResponse.user
+})
+```
+    
+## Connections
+
+Connections can be managed using `Usergrid.connect()`, `Usergrid.disconnect()`, and `Usergrid.getConnections()`, or entity convenience methods of the same name.
+
+### connect
+
+Create a connection between two entities:
+
+```js
+Usergrid.connect(entity1, 'relationship', entity2, function(error, usergridResponse) {
+    // entity1 now has an outbound connection to entity2
+})
+```
+    
+### getConnections
+
+Retrieve outbound connections:
+
+```js
+client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, 'relationship', function(error, usergridResponse, entities) {
+    // entities is an array of entities that entity1 is connected to via 'relationship'
+    // in this case, we'll see entity2 in the array
+})
+```
+    
+Retrieve inbound connections:
+
+```js
+client.getConnections(UsergridClient.Connections.DIRECTION_IN, entity2, 'relationship', function(error, usergridResponse, entities) {
+    // entities is an array of entities that connect to entity2 via 'relationship'
+    // in this case, we'll see entity1 in the array
+})```
+    
+### disconnect
+
+Delete a connection between two entities:
+
+```js
+Usergrid.disconnect(entity1, 'relationship', entity2, function(error, usergridResponse) {
+    // entity1's outbound connection to entity2 has been destroyed
+})
+```
+    
+## Assets
+
+Assets can be uploaded and downloaded either directly using `Usergrid.POST` or `Usergrid.PUT`, or via `UsergridEntity` convenience methods. Before uploading an asset, you will need to initialize a `UsergridAsset` instance.
+
+### UsergridAsset init
+
+Loading a file system image via `fs.readFile()`:
+
+```js
+var asset = new UsergridAsset('myImage')
+fs.readFile(_dirname + '/image.jpg', function(error, data) {
+    asset.data = data
+})
+```
+    
+Loading a file system image from a read stream (`fs.createReadStream()`):
+
+```js
+var asset = new UsergridAsset('myImage')
+fs.createReadStream(_dirname + '/image.jpg').pipe(asset).on('finish', function() {
+    // now contains Buffer stream at asset.data
+})
+```
+    
+You can also access `asset.contentType` and `asset.contentLength` once data has been loaded into a `UsergridAsset`.
+
+### .POST and .PUT
+
+POST binary data to a collection by creating a new entity:
+
+```js
+var asset = new UsergridAsset('myImage')
+fs.createReadStream(_dirname + '/image.jpg').pipe(asset).on('finish', function() {
+    client.POST('collection', asset, function(error, assetResponse, entityWithAsset) {
+        // asset is now uploaded to Usergrid
+    })
+})
+```
+
+PUT binary data to an existing entity via attachAsset():
+    
+```js
+var asset = new UsergridAsset('myImage')
+fs.createReadStream(_dirname + '/image.jpg').pipe(asset).on('finish', function() {
+    // assume entity already exists; attach it to the entity:
+    entity.attachAsset(asset)
+    client.PUT(entity, asset, function(error, assetResponse, entityWithAsset) {
+        // asset is now uploaded to Usergrid
+    })
+})
+```
+    
+### UsergridEntity convenience methods
+
+`entity.uploadAsset()` is a convenient way to upload an asset that is attached to an entity:
+
+```js
+var asset = new UsergridAsset('myImage')
+fs.createReadStream(_dirname + '/image.jpg').pipe(asset).on('finish', function() {
+    // assume entity already exists; attach it to the entity:
+    entity.attachAsset(asset)
+    entity.uploadAsset(function(error, assetResponse, entityWithAsset) {
+        // asset is now uploaded to Usergrid
+    })
+})
+```
+    
+`entity.downloadAsset()` allows you to download a binary asset:
+
+```js
+entity.uploadAsset(function(error, assetResponse, entityWithAsset) {
+    // access the asset via entityWithAsset.asset
+})```
diff --git a/changelog.md b/changelog.md
deleted file mode 100755
index c44ab04..0000000
--- a/changelog.md
+++ /dev/null
@@ -1,43 +0,0 @@
-##Change log

-

-

-###0.10.7

-- Fixed issue where token was appeneded even when no token was present

-- Updated tests

-

-

-###0.10.5

-

-- Added new class and methods for Groups

-- Added serialization / restore methods for entities and collections

-- Various bug fixes

-- Added function for getting user feed

-- Added function for creating user activities with an associated user entity

-- Added public facing helper method for signing up users

-

-###0.10.4

-

-- Added new functions for creating, getting, and deleting connections

-- Added test cases for said functions

-- Fixed change password error

-- Added getEntity method to get existing entity from server

-

-###0.10.3

-

-- Added set / get token methods to accomodate session storage

-- Added createUserActivity method to make creating activities for logged in user easier

-

-###0.10.2

-

-- Removed local caching of user object in client

-

-###0.10.1

-

-- Minor refactor of the SDK to bring congruity with the App services Javascript SDK

-

-###0.10.0

-- Complete refactor of the entire module

-

-- Added Mocha based test suite

-

-- Added full coverage of all sample code in the readme file

diff --git a/config.sample.json b/config.sample.json
new file mode 100644
index 0000000..4e6cfa9
--- /dev/null
+++ b/config.sample.json
@@ -0,0 +1,8 @@
+{
+    "appId": "yourAppId",
+    "authMode": "NONE",
+    "baseUrl": "https://api.usergrid.com",
+    "clientId": "yourClientId",
+    "clientSecret": "yourClientSecret",
+    "orgId": "yourOrgId"
+}
\ No newline at end of file
diff --git a/examples/api-proxy/README.md b/examples/api-proxy/README.md
new file mode 100644
index 0000000..db03d0a
--- /dev/null
+++ b/examples/api-proxy/README.md
@@ -0,0 +1,4 @@
+To use this example, rename the `usergrid.example.json` to `usergrid.json`.  
+This file is located at `examples/api-proxy/config`.
+
+**Note:** The usergrid module crawls your App file structure to find files named `usergrid.json` or a `config.json`.  If there are multiple files with one of these names present at different locations under the app, only one of them will be used and the others are ignored.  This may cause use of an unintended backend.  
diff --git a/examples/api-proxy/app.js b/examples/api-proxy/app.js
new file mode 100644
index 0000000..e0b85ec
--- /dev/null
+++ b/examples/api-proxy/app.js
@@ -0,0 +1,28 @@
+var express = require('express'),
+    app = express(),
+    Usergrid = require('usergrid')
+
+Usergrid.init()
+
+// Usergrid.setAppAuth(id, secret)
+// console.log(Usergrid.appAuth)
+Usergrid.authenticateApp(function(err, usergridResponse) {
+    if (usergridResponse.ok) {
+        console.log('app is now authenticated')
+    }
+})
+
+app.get('/:collection/:uuidOrName?', function(req, res) {
+    Usergrid.GET(req.params.collection, req.params.uuidOrName, function(error, usergridResponse, entities) {
+        res.json(entities);
+    })
+})
+
+// app.listen(process.env.port || 9000)
+
+/*
+
+1. Start the server using > node app.js
+2. Call the api at http://localhost:9000/cats/test
+
+*/
\ No newline at end of file
diff --git a/examples/api-proxy/config/usergrid.example.json b/examples/api-proxy/config/usergrid.example.json
new file mode 100644
index 0000000..36baab8
--- /dev/null
+++ b/examples/api-proxy/config/usergrid.example.json
@@ -0,0 +1,8 @@
+{
+    "appId": "sandbox",
+    "orgId": "brandon.apigee",
+    "authMode": "NONE",
+    "baseUrl": "https://api.usergrid.com",
+    "clientId": "YXA6GXSAACS2EeOYd20aP4G6Lw",
+    "clientSecret": "YXA66BeEvgNpJBwc4PAbvZZGTVS_SSw"
+}
\ No newline at end of file
diff --git a/examples/api-proxy/package.json b/examples/api-proxy/package.json
new file mode 100644
index 0000000..53deec5
--- /dev/null
+++ b/examples/api-proxy/package.json
@@ -0,0 +1,19 @@
+{
+    "dependencies": {
+        "async": "latest",
+        "express": "latest",
+        "usergrid": "r3mus/usergrid-nodejs",
+        "chance": "^0.8.0"
+    },
+    "description": "A sample API proxy built with Express and Usergrid",
+    "keywords": [],
+    "license": "MIT",
+    "main": "app.js",
+    "name": "usergrid-example-api-proxy",
+    "private": false,
+    "scripts": {
+        "start": "node app.js",
+        "test": "mocha tests"
+    },
+    "version": "2.0.0-rc.1"
+}
\ No newline at end of file
diff --git a/helpers/args.js b/helpers/args.js
new file mode 100644
index 0000000..6f221a5
--- /dev/null
+++ b/helpers/args.js
@@ -0,0 +1,21 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var _ = require('lodash')
+
+module.exports = function(args) {
+    return _.flattenDeep(Array.prototype.slice.call(args))
+}
\ No newline at end of file
diff --git a/helpers/build.js b/helpers/build.js
new file mode 100644
index 0000000..ca5ed43
--- /dev/null
+++ b/helpers/build.js
@@ -0,0 +1,479 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var urljoin = require('url-join'),
+    config = require('./config'),
+    helpers = require('./'),
+    UsergridQuery = require('../lib/query'),
+    UsergridEntity = require('../lib/entity'),
+    UsergridAuth = require('../lib/auth'),
+    UsergridAsset = require('../lib/asset'),
+    util = require('util'),
+    version = require('../package.json').version,
+    _ = require('lodash')
+
+var assignPrefabOptions = function(args) {
+    // if a preformatted options argument passed, assign it to options
+    if (_.isObject(args[0]) && !_.isFunction(args[0]) && _.has(this,"method")) {
+        _.assign(this, args[0])
+    }
+    return this
+}
+
+var setEntity = function(args) {
+    this.entity = _.first([this.entity, args[0]].filter(function(property) {
+        return (property instanceof UsergridEntity)
+    }))
+    if (this.entity !== undefined) {
+        this.type = this.entity.type
+    }
+    return this
+}
+
+var setAsset = function(args) {
+    this.asset = _.first([this.asset, _.get(this,'entity.asset'), args[1], args[0]].filter(function(property) {
+        return (property instanceof UsergridAsset)
+    }))
+    return this
+}
+
+var setUuidOrName = function(args) {
+    this.uuidOrName = _.first([
+        this.uuidOrName,
+        this.uuid,
+        this.name,
+        _.get(this,'entity.uuid'),
+        _.get(this,'body.uuid'),
+        _.get(this,'entity.name'),
+        _.get(this,'body.name'),
+        _.get(args,'2'),
+        _.get(args,'1')
+    ].filter(_.isString))
+    return this
+}
+
+var setPathOrType = function(args) {
+    var pathOrType = _.first([
+        this.type,
+        _.get(args,'0._type'),
+        _.get(this,'entity.type'),
+        _.get(this,'body.type'),
+        _.get(this,'body.0.type'),
+        _.isArray(args) ? args[0] : undefined
+    ].filter(_.isString))
+    this[(/\//.test(pathOrType)) ? 'path' : 'type'] = pathOrType
+    return this
+}
+
+var setQs = function(args) {
+    if (this.path) {
+        this.qs = _.first([this.qs, args[2], args[1], args[0]].filter(_.isPlainObject))
+    }
+    return this
+}
+
+var setQuery = function(args) {
+    this.query = _.first([this.query, args[0]].filter(function(property) {
+        return (property instanceof UsergridQuery)
+    }))
+    return this
+}
+
+var setBody = function(args) {
+    this.body = _.first([this.entity, this.body, args[2], args[1], args[0]].filter(function(property) {
+        return _.isObject(property) && !_.isFunction(property) && !(property instanceof UsergridQuery) && !(property instanceof UsergridAsset)
+    }))
+    if (this.body === undefined && this.asset === undefined) {
+        throw new Error(util.format('"body" is required when making a %s request', this.method))
+    }
+    return this
+}
+
+module.exports = {
+    uri: function(client, options) {
+        return urljoin(
+            client.baseUrl,
+            client.orgId,
+            client.appId,
+            options.path || options.type,
+            options.method !== "POST" ? _.first([
+                options.uuidOrName,
+                options.uuid,
+                options.name,
+                _.get(options,'entity.uuid'),
+                _.get(options,'entity.name'),
+                ""
+            ].filter(_.isString)) : ""
+        )
+    },
+    headers: function(client, options) {
+        var headers = {
+            'User-Agent': util.format("usergrid-nodejs/v%s", version)
+        }
+
+        _.assign(headers, options.headers)
+
+        var token
+        var clientTempAuth = _.get(client,"tempAuth")
+        if( !_.isUndefined(clientTempAuth) ) {
+            if( clientTempAuth !== UsergridAuth.NO_AUTH && clientTempAuth.isValid ) {
+                token = client.tempAuth.token;
+            }
+            client.tempAuth = undefined
+        } else {
+            var clientAuthMode = _.get(client,"authMode");
+            if( _.get(client,"currentUser.auth.isValid") && clientAuthMode === UsergridAuth.AUTH_MODE_USER ) {
+                token = client.currentUser.auth.token;
+            } else if( _.get(client,"appAuth.isValid") && clientAuthMode === UsergridAuth.AUTH_MODE_APP ) {
+                token = client.appAuth.token;
+            }
+        }
+
+        if (token) {
+            _.assign(headers, {
+                authorization: util.format("Bearer %s", token)
+            })
+        }
+
+        return headers
+    },
+    userLoginBody: function(options) {
+        var body = {
+            grant_type: 'password',
+            password: options.password
+        }
+        if (options.tokenTtl) {
+            body.ttl = options.tokenTtl
+        }
+        body[(options.username) ? "username" : "email"] = (options.username) ? options.username : options.email
+        return body
+    },
+    appLoginBody: function(options) {
+        var body = {
+            grant_type: 'client_credentials',
+            client_id: options.clientId,
+            client_secret: options.clientSecret
+        }
+        if (options.tokenTtl) {
+            body.ttl = options.tokenTtl
+        }
+        return body
+    },
+    GET: function(client, args) {
+
+        /* GET supports the following constructor patterns:
+
+        client.GET('type', 'uuidOrName', optionalCallback)
+        client.GET('type', optionalCallback)
+        client.GET(query, optionalCallback)
+        client.GET({
+            query: query, // takes precedence
+            type: type, // required if query not defined
+            uuid: uuid, // will be set to uuidOrName on init (priority)
+            name: name, // will be set to uuidOrName on init (if no uuid specified)
+            uuidOrName: uuidOrName // the definitive key for name or uuid
+        }, optionalCallback)
+
+        */
+
+        var options = {
+            client: client,
+            method: 'GET',
+            callback: helpers.cb(args)
+        }
+        assignPrefabOptions.call(options, args)
+        setEntity.call(options, args)
+        setUuidOrName.call(options, args)
+        setPathOrType.call(options, args)
+        setQs.call(options, args)
+        setQuery.call(options, args)
+        return options
+    },
+    PUT: function(client, args) {
+
+        /* PUT supports the following constructor patterns:
+
+        client.PUT('type', 'uuidOrName', bodyObject, optionalCallback)
+        client.PUT('type', bodyObject, optionalCallback) // if no uuid, will create a new record
+        client.PUT(bodyObjectOrEntity, optionalCallback) // if no uuid, will create a new record; must include type
+        client.PUT(query, bodyObjectOrEntity, optionalCallback) // will update all entities matching query
+        client.PUT(entity, optionalCallback)
+        client.PUT({
+            *entity = alias to body*
+            query: query, // takes precedence over type/body
+            type: type, // required if query not defined
+            body: bodyObject or bodyObjectOrEntity, // if includes type, type will be inferred from body
+            *uuid, name* = alias to uuidOrName*
+            uuidOrName: uuidOrName // the definitive key for name or uuid
+        }, optionalCallback)
+
+        */
+
+        var options = {
+            client: client,
+            method: 'PUT',
+            callback: helpers.cb(args)
+        }
+        assignPrefabOptions.call(options, args)
+        setEntity.call(options, args)
+        setAsset.call(options, args)
+        setBody.call(options, args)
+        setUuidOrName.call(options, args)
+        setPathOrType.call(options, args)
+        setQuery.call(options, args)
+        return options
+    },
+    POST: function(client, args) {
+
+        /* POST supports the following constructor patterns:
+
+        client.POST('type', bodyObjectOrArray, optionalCallback)
+        client.POST(bodyObjectOrArray, optionalCallback) // must include type in body
+        client.POST(entityOrEntities, optionalCallback)
+        client.POST({
+            *entity, entities = alias to body*
+            type: type, // required
+            body: bodyObjectOrArray or entityOrEntities, // if the first entity includes type, type will be inferred from body
+        }, optionalCallback)
+
+        */
+
+        var options = {
+            client: client,
+            method: 'POST',
+            callback: helpers.cb(args)
+        }
+        assignPrefabOptions.call(options, args)
+        setEntity.call(options, args)
+        setAsset.call(options, args)
+        setBody.call(options, args)
+        setPathOrType.call(options, args)
+        return options
+    },
+    DELETE: function(client, args) {
+
+        /* DELETE supports the following constructor patterns:
+
+        client.DELETE('type', 'uuidOrName', optionalCallback)
+        client.DELETE(entity, optionalCallback) // must include type in body
+        client.DELETE(query, optionalCallback)
+        client.DELETE({
+            *uuid, name* = alias to uuidOrName*
+            uuidOrName: uuidOrName,
+            type: type, // required if query not defined
+            query: query // takes precedence over type/uuid
+        }, optionalCallback)
+
+        */
+
+        var options = {
+            client: client,
+            method: 'DELETE',
+            callback: helpers.cb(args)
+        }
+        assignPrefabOptions.call(options, args)
+        setEntity.call(options, args)
+        setUuidOrName.call(options, args)
+        setPathOrType.call(options, args)
+        setQs.call(options, args)
+        setQuery.call(options, args)
+        return options
+    },
+    connection: function(client, method, args) {
+
+        /* connect supports the following constructor patterns:
+
+        client.connect(entity, "relationship", toEntity);
+        // POST entity.type/entity.uuid/"relationship"/toEntity.uuid
+
+        client.connect("type", <uuidOrName>, "relationship", <toUuid>);
+        // POST type/uuidOrName/relationship/toUuid
+
+        client.connect("type", <uuidOrName>, "relationship", "toType", "toName");
+        // POST type/uuidOrName/relationship/toType/toName
+
+        client.connect({
+            entity: { // or UsergridEntity
+                type: "type", 
+                uuidOrName: <uuidOrName>
+            },
+            relationship: "likes",
+            to: { // or UsergridEntity
+                "type": "(required if not using uuid)",
+                "uuidOrName": <uuidOrName>,
+                "name": "alias to uuidOrName" // if uuid not specified, requires "type"
+                "uuid": "alias to uuidOrName" 
+            }
+        );
+
+        disconnect supports the identical patters, but uses DELETE instead of POST; it is therefore a reference to this function
+
+        */
+
+        var options = {
+            client: client,
+            method: method,
+            entity: {},
+            to: {},
+            callback: helpers.cb(args)
+        }
+
+        assignPrefabOptions.call(options, args)
+
+        // handle DELETE using "from" preposition
+        if (_.isObject(options.from)) {
+            options.to = options.from
+        }
+
+        // if an entity object or UsergridEntity instance is the first argument (source)
+        if (_.isObject(args[0]) && !_.isFunction(args[0]) && _.isString(args[1])) {
+            _.assign(options.entity, args[0])
+            options.relationship = _.first([options.relationship, args[1]].filter(_.isString))
+        }
+
+        // if an entity object or UsergridEntity instance is the third argument (target)
+        if (_.isObject(args[2]) && !_.isFunction(args[2])) {
+            _.assign(options.to, args[2])
+        }
+
+        options.entity.uuidOrName = _.first([options.entity.uuidOrName, options.entity.uuid, options.entity.name, args[1]].filter(_.isString))
+        if (!options.entity.type) {
+            options.entity.type = _.first([options.entity.type, args[0]].filter(_.isString))
+        }
+        options.relationship = _.first([options.relationship, args[2]].filter(_.isString))
+
+        if (_.isString(args[3]) && !_.isUuid(args[3]) && _.isString(args[4])) {
+            options.to.type = args[3]
+        } else if (_.isString(args[2]) && !_.isUuid(args[2]) && _.isString(args[3]) && _.isObject(args[0]) && !_.isFunction(args[0])) {
+            options.to.type = args[2]
+        }
+
+        options.to.uuidOrName = _.first([options.to.uuidOrName, options.to.uuid, options.to.name, args[4], args[3], args[2]].filter(function(property) {
+            return (_.isString(options.to.type) && _.isString(property) || _.isUuid(property))
+        }))
+
+        if (!_.isString(options.entity.uuidOrName)) {
+            throw new Error('source entity "uuidOrName" is required when connecting or disconnecting entities')
+        }
+
+        if (!_.isString(options.to.uuidOrName)) {
+            throw new Error('target entity "uuidOrName" is required when connecting or disconnecting entities')
+        }
+
+        if (!_.isString(options.to.type) && !_.isUuid(options.to.uuidOrName)) {
+            throw new Error('target "type" (collection name) parameter is required connecting or disconnecting entities by name')
+        }
+
+        options.uri = urljoin(
+            config.baseUrl,
+            client.orgId,
+            client.appId,
+            _.isString(options.entity.type) ? options.entity.type : "",
+            _.isString(options.entity.uuidOrName) ? options.entity.uuidOrName : "",
+            options.relationship,
+            _.isString(options.to.type) ? options.to.type : "",
+            _.isString(options.to.uuidOrName) ? options.to.uuidOrName : ""
+        )
+
+        return options
+    },
+    getConnections: function(client, args) {
+        /* getConnections supports the following constructor patterns:
+
+        client.getConnections(direction, entity, "relationship");
+        // GET OUT: /entity.type/entity.uuid/connections/relationship
+        // GET IN: /entity.type/entity.uuid/connecting/relationship
+
+        client.getConnections(direction, "type", "<uuidOrName>", "relationship");
+        // GET OUT: /type/uuidOrName/connections/relationship
+        // GET IN: /type/uuidOrName/connecting/relationship
+
+        client.getConnections({
+            type: "type", // or inferred, if second argument is an entity
+            uuidOrName: "<uuidOrName>" // if entity not specified
+            relationship: "relationship",
+            direction: OUT or IN
+        );
+        // GET OUT: /entity.type/entity.uuid/connections/relationship
+        // GET IN: /entity.type/entity.uuid/connecting/relationship
+
+        */
+
+        var options = {
+            client: client,
+            method: 'GET',
+            callback: helpers.cb(args)
+        }
+
+        assignPrefabOptions.call(options, args)
+        if (_.isObject(args[1]) && !_.isFunction(args[1])) {
+            _.assign(options, args[1])
+        }
+
+        options.direction = _.first([options.direction, args[0]].filter(function(property) {
+            return (property === "IN" || property === "OUT")
+        }))
+
+        options.relationship = _.first([options.relationship, args[3], args[2]].filter(_.isString))
+        options.uuidOrName = _.first([options.uuidOrName, options.uuid, options.name, args[2]].filter(_.isString))
+        options.type = _.first([options.type, args[1]].filter(_.isString))
+
+        if (!_.isString(options.type)) {
+            throw new Error('"type" (collection name) parameter is required when retrieving connections')
+        }
+
+        if (!_.isString(options.uuidOrName)) {
+            throw new Error('target entity "uuidOrName" is required when retrieving connections')
+        }
+
+        options.uri = urljoin(
+            config.baseUrl,
+            client.orgId,
+            client.appId,
+            _.isString(options.type) ? options.type : "",
+            _.isString(options.uuidOrName) ? options.uuidOrName : "",
+            options.direction === "IN" ? "connecting" : "connections",
+            options.relationship
+        )
+
+        return options
+    },
+    qs: function(options) {
+        return (options.query instanceof UsergridQuery) ? {
+            ql: options.query._ql || undefined,
+            limit: options.query._limit,
+            cursor: options.query._cursor
+        } : options.qs
+    },
+    formData: function(options) {
+        if (_.get(options,'asset.data')) {
+            var formData = {}
+            formData.file = {
+                value: options.asset.data,
+                options: {
+                    filename: _.get(options,'asset.filename') || UsergridAsset.DEFAULT_FILE_NAME,
+                    contentType: _.get(options,'asset.contentType') || 'application/octet-stream'
+                }
+            } 
+            if (_.has(options,'asset.name')) {
+                formData.name = options.asset.name
+            }
+            return formData
+        } else {
+            return undefined
+        }
+    }
+}
\ No newline at end of file
diff --git a/helpers/cb.js b/helpers/cb.js
new file mode 100644
index 0000000..e26076d
--- /dev/null
+++ b/helpers/cb.js
@@ -0,0 +1,24 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var _ = require('lodash')
+
+module.exports = function() {
+    var args = _.flattenDeep(Array.prototype.slice.call(arguments)).reverse()
+    var emptyFunc = function() {}
+    return _.first(_.flattenDeep([args, _.get(args,'0.callback'), emptyFunc]).filter(_.isFunction))
+
+}
\ No newline at end of file
diff --git a/helpers/client.js b/helpers/client.js
new file mode 100644
index 0000000..a9273de
--- /dev/null
+++ b/helpers/client.js
@@ -0,0 +1,48 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var Usergrid = require('../usergrid'),
+    _ = require('lodash')
+
+
+module.exports = {
+    validate: function(args) {
+        var UsergridClient = require('../lib/client')
+        var client
+        if (args instanceof UsergridClient) {
+            client = args
+        } else if (args[0] instanceof UsergridClient) {
+            client = args[0]
+        } else if (Usergrid.isInitialized) {
+            client = Usergrid
+        } else {
+            throw new Error("this method requires either the Usergrid shared instance to be initialized or a UsergridClient instance as the first argument")
+        } 
+        return client
+    },
+    configureTempAuth: function(auth) {
+        var UsergridAuth = require('../lib/auth')
+        if (_.isString(auth) && auth !== UsergridAuth.NO_AUTH) {
+            return new UsergridAuth(auth)
+        } else if (!auth || auth === UsergridAuth.NO_AUTH) {
+            return UsergridAuth.NO_AUTH
+        } else if (auth instanceof UsergridAuth) {
+            return auth
+        } else {
+            return undefined
+        }
+    }
+}
\ No newline at end of file
diff --git a/helpers/config.js b/helpers/config.js
new file mode 100644
index 0000000..ee6e0b6
--- /dev/null
+++ b/helpers/config.js
@@ -0,0 +1,52 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var util = require('util'),
+    path = require('path'),
+    file = require("file"),
+    _ = require('lodash'),
+    appRoot = path.dirname(require.main.filename)
+
+if (/mocha$/i.test(process.argv[1])) {
+    var target = (_.last(process.argv)).startsWith('--target=') ? _.last(process.argv).replace(/--target=/, '') : '2.1'
+    var config = require('../tests/config.test.json')[target]
+    if (config && target) {
+        config.target = target
+    } else {
+        throw new Error(util.format("Could not load target '%s' from /tests/config.test.json", target))
+    }
+    module.exports = config
+} else {
+    try {
+        file.walkSync(appRoot, function(start, dirs, names) {
+            if (_.includes(names, "config.json") || _.includes(names, "usergrid.json")) {
+                var name = _.first(names.filter(function(name) {
+                    return name === "config.json" || name === "usergrid.json"
+                }).sort().reverse())
+                var configPath = util.format("%s/%s", start, name)
+                module.exports = require(configPath)
+                if (module.exports.orgId === undefined || module.exports.appId === undefined) {
+                    console.log(util.format("Config file '%s' is not a valid Usergrid configuration file", configPath))
+                    module.exports = {}
+                } else {
+                    console.log(util.format("Using config file '%s'", configPath))
+                }
+            }
+        })
+    } catch (e) {
+
+    }
+}
\ No newline at end of file
diff --git a/helpers/index.js b/helpers/index.js
new file mode 100644
index 0000000..6a46b6d
--- /dev/null
+++ b/helpers/index.js
@@ -0,0 +1,40 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var args = require('./args'),
+    client = require('./client'),
+    cb = require('./cb'),
+    build = require('./build'),
+    query = require('./query'),
+    config = require('./config'),
+    time = require('./time'),
+    mutability = require('./mutability'),
+    user = require('./user'),
+    _ = require('lodash')
+
+// by mixing this in here, lodash-uuid is available everywhere lodash is used.
+_.mixin(require('lodash-uuid'))
+
+module.exports = _.assign(module.exports, {
+    args: args,
+    client: client,
+    cb: cb,
+    build: build,
+    query: query,
+    config: config,
+    time: time,
+    user: user
+}, mutability)
\ No newline at end of file
diff --git a/helpers/mutability.js b/helpers/mutability.js
new file mode 100644
index 0000000..0ad058b
--- /dev/null
+++ b/helpers/mutability.js
@@ -0,0 +1,55 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var _ = require('lodash')
+
+module.exports = {
+    setReadOnly: function(obj, key) {
+        if (_.isArray(key)) {
+            return key.forEach(function(k) {
+                module.exports.setReadOnly(obj, k)
+            })
+        } else if (_.isPlainObject(obj[key])) {
+            return Object.freeze(obj[key])
+        } else if (_.isPlainObject(obj) && key === undefined) {
+            return Object.freeze(obj)
+        } else if (_.has(obj,key)) {
+            return Object.defineProperty(obj, key, {
+                writable: false
+            })
+        } else {
+            return obj
+        }
+    },
+    setWritable: function(obj, key) {
+        if (_.isArray(key)) {
+            return key.forEach(function(k) {
+                module.exports.setWritable(obj, k)
+            })
+        // Note that once Object.freeze is called on an object, it cannot be unfrozen, so we need to clone it
+        } else if (_.isPlainObject(obj[key])) {
+            return _.clone(obj[key])
+        } else if (_.isPlainObject(obj) && key === undefined) {
+            return _.clone(obj)
+        } else if (_.has(obj,key)) {
+            return Object.defineProperty(obj, key, {
+                writable: true
+            })
+        } else {
+            return obj
+        }
+    }
+}
\ No newline at end of file
diff --git a/helpers/query.js b/helpers/query.js
new file mode 100644
index 0000000..f019545
--- /dev/null
+++ b/helpers/query.js
@@ -0,0 +1,24 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var util = require('util'),
+    _ = require('lodash')
+
+module.exports = {
+    useQuotesIfRequired: function(value) {
+        return (_.isFinite(value) || _.isUuid(value) || _.isBoolean(value) || _.isObject(value) && !_.isFunction(value) || _.isArray(value)) ? value : util.format("'%s'", value)
+    }
+}
\ No newline at end of file
diff --git a/helpers/time.js b/helpers/time.js
new file mode 100644
index 0000000..d3452b1
--- /dev/null
+++ b/helpers/time.js
@@ -0,0 +1,21 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+module.exports = {
+    expiry: function(expires_in) {
+        return Date.now() + ((expires_in ? expires_in - 5 : 0) * 1000)
+    }
+}
\ No newline at end of file
diff --git a/helpers/user.js b/helpers/user.js
new file mode 100644
index 0000000..45dcde4
--- /dev/null
+++ b/helpers/user.js
@@ -0,0 +1,23 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var _ = require('lodash')
+
+module.exports = {
+    uniqueId: function(user) {
+        return _.first([user.uuid, user.username, user.email].filter(_.isString))
+    }
+}
\ No newline at end of file
diff --git a/lib/appAuth.js b/lib/appAuth.js
new file mode 100644
index 0000000..740002e
--- /dev/null
+++ b/lib/appAuth.js
@@ -0,0 +1,41 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var UsergridAuth = require('./auth'),
+    helpers = require('../helpers'),
+    util = require('util'),
+    _ = require('lodash')
+
+var UsergridAppAuth = function() {
+    var self = this
+    var args = _.flattenDeep(helpers.args(arguments))
+    if (_.isPlainObject(args[0])) {
+        self.clientId = args[0].clientId
+        self.clientSecret = args[0].clientSecret
+        self.tokenTtl = args[0].tokenTtl
+    } else {
+        self.clientId = args[0]
+        self.clientSecret = args[1]
+        self.tokenTtl = args[2]
+    }
+    UsergridAuth.call(self)
+    _.assign(self, UsergridAuth)
+    return self
+}
+
+util.inherits(UsergridAppAuth, UsergridAuth)
+
+module.exports = UsergridAppAuth
\ No newline at end of file
diff --git a/lib/asset.js b/lib/asset.js
new file mode 100644
index 0000000..a054075
--- /dev/null
+++ b/lib/asset.js
@@ -0,0 +1,86 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var fileType = require('file-type'),
+    helpers = require('../helpers'),
+    stream = require('stream'),
+    util = require('util'),
+    _ = require('lodash')
+
+var UsergridAsset = function() {
+    var self = this
+    var args = helpers.args(arguments)
+
+    var __contentType
+    var __binaryData = []
+
+    if (args.length === 0) {
+        throw new Error('A UsergridAsset object cannot be initialized without passing one or more arguments')
+    }
+
+    if (_.isPlainObject(args[0])) {
+        _.assign(self, args[0])
+    } else {
+        self.filename = _.isString(args[0]) ? args[0] : undefined
+        self.data = _.first(args.filter(Buffer.isBuffer)) || []
+        self.originalLocation = _.first([args[2], args[1]].filter(function(property) {
+            return _.isString(property)
+        }))
+
+        self.contentType = _.isString(args[3]) ? args[3] : undefined
+
+        stream.PassThrough.call(self)
+        self._write = function(chunk, encoding, done) {
+            __binaryData.push(chunk)
+            done()
+        }
+        self.on('finish', function() {
+            self.data = Buffer.concat(__binaryData)
+        })
+    }
+
+    Object.defineProperty(self, 'contentLength', {
+        get: function() {
+            return (self.data) ? self.data.byteLength : 0
+        }
+    })
+
+    Object.defineProperty(self, 'contentType', {
+        get: function() {
+            if (__contentType) {
+                return __contentType
+            } else if (Buffer.isBuffer(self.data)) {
+                __contentType = fileType(self.data) != null ? fileType(self.data).mime : undefined
+                return __contentType
+            }
+        },
+        set: function(contentType) {
+            if (contentType) {
+                __contentType = contentType
+            } else if (Buffer.isBuffer(self.data)) {
+                __contentType = fileType(self.data) != null ? fileType(self.data).mime : undefined
+            }
+        }
+    })
+
+    return self
+}
+
+util.inherits(UsergridAsset, stream.PassThrough)
+
+
+module.exports = UsergridAsset
+module.exports.DEFAULT_FILE_NAME = 'file'
\ No newline at end of file
diff --git a/lib/auth.js b/lib/auth.js
new file mode 100644
index 0000000..06ef684
--- /dev/null
+++ b/lib/auth.js
@@ -0,0 +1,79 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var UsergridAuth = function(token, expiry) {
+    var self = this
+
+    self.token = token
+    self.expiry = expiry || 0
+
+    var usingToken = (token) ? true : false
+
+    Object.defineProperty(self, "hasToken", {
+        get: function() {
+            return (self.token) ? true : false
+        },
+        configurable: true
+    })
+
+    Object.defineProperty(self, "isExpired", {
+        get: function() {
+            return (usingToken) ? false : (Date.now() >= self.expiry)
+        },
+        configurable: true
+    })
+
+    Object.defineProperty(self, "isValid", {
+        get: function() {
+            return (!self.isExpired && self.hasToken)
+        },
+        configurable: true
+    })
+
+    Object.defineProperty(self, 'tokenTtl', {
+        configurable: true,
+        writable: true
+    })
+
+    return self
+}
+
+UsergridAuth.prototype = {
+    destroy: function() {
+        this.token = undefined
+        this.expiry = 0
+        this.tokenTtl = undefined
+    }
+}
+
+module.exports = UsergridAuth
+
+Object.defineProperty(module.exports, 'AUTH_MODE_APP', {
+    enumerable: false,
+    get: function() { return "APP" }
+})
+Object.defineProperty(module.exports, 'AUTH_MODE_USER', {
+    enumerable: false,
+    get: function() { return "USER" }
+})
+Object.defineProperty(module.exports, 'AUTH_MODE_NONE', {
+    enumerable: false,
+    get: function() { return "NONE" }
+})
+Object.defineProperty(module.exports, 'NO_AUTH', {
+    enumerable: false,
+    get: function() { return "NO_AUTH" }
+})
\ No newline at end of file
diff --git a/lib/client.js b/lib/client.js
new file mode 100644
index 0000000..3f26d0f
--- /dev/null
+++ b/lib/client.js
@@ -0,0 +1,170 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var helpers = require('../helpers'),
+    UsergridRequest = require('./request'),
+    UsergridAuth = require('./auth'),
+    UsergridAppAuth = require('./appAuth'),
+    _ = require('lodash')
+
+var defaultOptions = {
+    baseUrl: 'https://api.usergrid.com',
+    authMode: UsergridAuth.AUTH_MODE_USER
+}
+
+var UsergridClient = function(options) {
+    var self = this
+
+    var __appAuth
+    self.tempAuth = undefined
+    self.isSharedInstance = false
+
+    if (arguments.length === 2) {
+        self.orgId = arguments[0]
+        self.appId = arguments[1]
+    }
+
+    _.defaults(self, options, helpers.config, defaultOptions)
+
+    if (!self.orgId || !self.appId) {
+        throw new Error('"orgId" and "appId" parameters are required when instantiating UsergridClient')
+    }
+
+    Object.defineProperty(self, 'test', {
+        enumerable: false
+    })
+
+    Object.defineProperty(self, 'clientId', {
+        enumerable: false
+    })
+
+    Object.defineProperty(self, 'clientSecret', {
+        enumerable: false
+    })
+
+    Object.defineProperty(self, 'appAuth', {
+        get: function() {
+            return __appAuth
+        },
+        set: function(options) {
+            if (options instanceof UsergridAppAuth) {
+                __appAuth = options
+            } else if( _.isUndefined(options) ) {
+                __appAuth = undefined
+            } else {
+                __appAuth = new UsergridAppAuth(options)
+            }
+        }
+    })
+
+    // if client ID and secret are defined on initialization, initialize appAuth
+    if (self.clientId && self.clientSecret) {
+        self.setAppAuth(self.clientId, self.clientSecret)
+    }
+    return self
+}
+
+UsergridClient.prototype = {
+    GET: function() {
+        return new UsergridRequest(helpers.build.GET(this, helpers.args(arguments)))
+    },
+    PUT: function() {
+        return new UsergridRequest(helpers.build.PUT(this, helpers.args(arguments)))
+    },
+    POST: function() {
+        return new UsergridRequest(helpers.build.POST(this, helpers.args(arguments)))
+    },
+    DELETE: function() {
+        return new UsergridRequest(helpers.build.DELETE(this, helpers.args(arguments)))
+    },
+    connect: function() {
+        return new UsergridRequest(helpers.build.connection(this, 'POST', helpers.args(arguments)))
+    },
+    disconnect: function() {
+        return new UsergridRequest(helpers.build.connection(this, 'DELETE', helpers.args(arguments)))
+    },
+    getConnections: function() {        
+        return new UsergridRequest(helpers.build.getConnections(this, helpers.args(arguments)))
+    },
+    setAppAuth: function() {
+        this.appAuth = new UsergridAppAuth(helpers.args(arguments))
+    },
+    authenticateApp: function(options) {
+        var self = this
+        var callback = helpers.cb(helpers.args(arguments))
+        // console.log(self.appAuth)//, self.appAuth, new UsergridAppAuth(options), new UsergridAppAuth(self.clientId, self.clientSecret))
+        var auth = _.first([options, self.appAuth, new UsergridAppAuth(options), new UsergridAppAuth(self.clientId, self.clientSecret)].filter(function(p) {
+            return p instanceof UsergridAppAuth
+        }))
+
+        if (!(auth instanceof UsergridAppAuth)) {
+            throw new Error('App auth context was not defined when attempting to call .authenticateApp()')
+        } else if (!auth.clientId || !auth.clientSecret) {
+            throw new Error('authenticateApp() failed because clientId or clientSecret are missing')
+        }
+
+        return new UsergridRequest({
+            client: self,
+            path: 'token',
+            method: 'POST',
+            body: helpers.build.appLoginBody(auth)
+        }, function(error, usergridResponse, body) {
+            if (usergridResponse.ok) {
+                if (!self.appAuth) {
+                    self.appAuth = auth
+                }
+                self.appAuth.token = body.access_token
+                self.appAuth.expiry = helpers.time.expiry(body.expires_in)
+                self.appAuth.tokenTtl = body.expires_in
+            }
+            callback(error, usergridResponse, body.access_token)
+        })
+    },
+    authenticateUser: function(options) {
+        var self = this
+        var args = helpers.args(arguments)
+        var callback = helpers.cb(args)
+        var setAsCurrentUser = (_.last(args.filter(_.isBoolean))) !== undefined ? _.last(args.filter(_.isBoolean)) : true
+        var UsergridUser = require('./user')
+        var currentUser = new UsergridUser(options)
+        currentUser.login(self, function(error, usergridResponse, token) {
+            if (usergridResponse.ok && setAsCurrentUser) {
+                self.currentUser = currentUser
+            }
+            callback(error, usergridResponse, token)
+        })
+    },
+    usingAuth: function(auth) {
+        this.tempAuth = helpers.client.configureTempAuth(auth)
+        return this
+    }
+}
+
+module.exports = UsergridClient
+Object.defineProperty(module.exports, 'Connections', {
+    enumerable: false,
+    writable: true,
+    configurable: true
+})
+module.exports.Connections = {}
+Object.defineProperty(module.exports.Connections, 'DIRECTION_IN', {
+    enumerable: false,
+    get: function() { return "IN" }
+})
+Object.defineProperty(module.exports.Connections, 'DIRECTION_OUT', {
+    enumerable: false,
+    get: function() { return "OUT" }
+})
\ No newline at end of file
diff --git a/lib/entity.js b/lib/entity.js
new file mode 100644
index 0000000..b1452a6
--- /dev/null
+++ b/lib/entity.js
@@ -0,0 +1,225 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var UsergridRequest = require('./request'),
+    UsergridAsset = require('./asset'),
+    helpers = require('../helpers'),
+    _ = require('lodash')
+
+function updateEntityFromRemote(usergridResponse) {
+    helpers.setWritable(this, ['uuid', 'name', 'type', 'created'])
+    _.assign(this, usergridResponse.entity)
+    helpers.setReadOnly(this, ['uuid', 'name', 'type', 'created'])
+}
+
+var UsergridEntity = function() {
+    var self = this
+    var args = helpers.args(arguments)
+
+    if (args.length === 0) {
+        throw new Error('A UsergridEntity object cannot be initialized without passing one or more arguments')
+    }
+
+    var firstArg = args[0]
+    if (_.isPlainObject(firstArg) || firstArg instanceof UsergridEntity ) {
+        _.assign(self, args[0]);
+    } else {
+        if( !self.type ) {
+            self.type = _.isString(args[0]) ? args[0] : undefined;
+        }
+        if( !self.name ) {
+            self.name = _.isString(args[1]) ? args[1] : undefined;
+        }
+    }
+
+    if (!_.isString(self.type)) {
+        throw new Error('"type" (or "collection") parameter is required when initializing a UsergridEntity object')
+    }
+
+    Object.defineProperty(self, 'tempAuth', {
+        enumerable: false,
+        configurable: true,
+        writable: true
+    })
+
+    Object.defineProperty(self, 'asset', {
+        enumerable: false,
+        configurable: true,
+        writable: true
+    })
+
+    Object.defineProperty(self, 'isUser', {
+        get: function() {
+            return (self.type.toLowerCase() === 'user')
+        }
+    })
+
+    Object.defineProperty(self, 'hasAsset', {
+        get: function() {
+            return _.has(self,'file-metadata')
+        }
+    })
+
+    helpers.setReadOnly(self, ['uuid', 'name', 'type', 'created'])
+
+    return self
+}
+
+UsergridEntity.prototype = {
+    putProperty: function(key, value) {
+        this[key] = value
+    },
+    putProperties: function(obj) {
+        _.assign(this, obj)
+    },
+    removeProperty: function(key) {
+        this.removeProperties([key])
+    },
+    removeProperties: function(keys) {
+        var self = this
+        keys.forEach(function(key) {
+            delete self[key]
+        })
+    },
+    insert: function(key, value, idx) {
+        if (!_.isArray(this[key])) {
+            this[key] = this[key] ? [this[key]] : []
+        }
+        this[key].splice.apply(this[key], [idx, 0].concat(value))
+    },
+    append: function(key, value) {
+        this.insert(key, value, Number.MAX_SAFE_INTEGER)
+    },
+    prepend: function(key, value) {
+        this.insert(key, value, 0)
+    },
+    pop: function(key) {
+        if (_.isArray(this[key])) {
+            this[key].pop()
+        }
+    },
+    shift: function(key) {
+        if (_.isArray(this[key])) {
+            this[key].shift()
+        }
+    },
+    reload: function() {
+        var args = helpers.args(arguments)
+        var client = helpers.client.validate(args)
+        client.tempAuth = this.tempAuth
+        this.tempAuth = undefined
+        var callback = helpers.cb(args)
+        client.GET(this, function(error, usergridResponse) {
+            updateEntityFromRemote.call(this, usergridResponse)
+            callback(error, usergridResponse, this)
+        }.bind(this))
+    },
+    save: function() {
+        var args = helpers.args(arguments)
+        var client = helpers.client.validate(args)
+        client.tempAuth = this.tempAuth
+        this.tempAuth = undefined
+        var callback = helpers.cb(args)
+        client.PUT(this, function(error, usergridResponse) {
+            updateEntityFromRemote.call(this, usergridResponse)
+            callback(error, usergridResponse, this)
+        }.bind(this))
+    },
+    remove: function() {
+        var args = helpers.args(arguments)
+        var client = helpers.client.validate(args)
+        client.tempAuth = this.tempAuth
+        this.tempAuth = undefined
+        var callback = helpers.cb(args)
+        client.DELETE(this, function(error, usergridResponse) {
+            callback(error, usergridResponse, this)
+        }.bind(this))
+    },
+    attachAsset: function(asset) {
+        this.asset = asset
+    },
+    uploadAsset: function() {
+        var args = helpers.args(arguments)
+        var client = helpers.client.validate(args)
+        var callback = helpers.cb(args)
+        client.POST(this, this.asset, function(error, usergridResponse) {
+            updateEntityFromRemote.call(this, usergridResponse)
+            callback(error, usergridResponse, this)
+        }.bind(this))
+    },
+    downloadAsset: function() {
+        var args = helpers.args(arguments)
+        var client = helpers.client.validate(args)
+        var callback = helpers.cb(args)
+        var self = this
+
+        if (_.has(self,'asset.contentType')) {
+            var options = {
+                client: client,
+                entity: self,
+                type: this.type,
+                method: 'GET',
+                encoding: null,
+                headers: {
+                    "Accept": self.asset.contentType || _.first(args.filter(_.isString))
+                }
+            }
+            options.uri = helpers.build.uri(client, options)
+            return new UsergridRequest(options, function(error, usergridResponse) {
+                if (usergridResponse.ok) {
+                    self.attachAsset(new UsergridAsset(new Buffer(usergridResponse.body)))
+                }
+                callback(error, usergridResponse, self)
+            })
+        } else {
+            callback({
+                name: "asset_not_found",
+                description: "The specified entity does not have a valid asset attached"
+            })
+        }
+    },
+    connect: function() {
+        var args = helpers.args(arguments)
+        var client = helpers.client.validate(args)
+        client.tempAuth = this.tempAuth
+        this.tempAuth = undefined
+        args[0] = this
+        return client.connect.apply(client, args)
+    },
+    disconnect: function() {
+        var args = helpers.args(arguments)
+        var client = helpers.client.validate(args)
+        client.tempAuth = this.tempAuth
+        this.tempAuth = undefined
+        args[0] = this
+        return client.disconnect.apply(client, args)
+    },
+    getConnections: function() {
+        var args = helpers.args(arguments)
+        var client = helpers.client.validate(args)
+        client.tempAuth = this.tempAuth
+        this.tempAuth = undefined
+        args.shift()
+        args.splice(1, 0, this)
+        return client.getConnections.apply(client, args)
+    },
+    usingAuth: function(auth) {
+        this.tempAuth = helpers.client.configureTempAuth(auth)
+        return this
+    }
+}
+
+module.exports = UsergridEntity
\ No newline at end of file
diff --git a/lib/query.js b/lib/query.js
new file mode 100644
index 0000000..9cbfd5c
--- /dev/null
+++ b/lib/query.js
@@ -0,0 +1,153 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var helpers = require('../helpers'),
+    util = require('util'),
+    _ = require('lodash')
+
+var UsergridQuery = function(type) {
+
+    var self = this
+
+    var query = '',
+        queryString,
+        sort,
+        __nextIsNot = false
+
+    // builder pattern
+    _.assign(self, {
+        type: function(value) {
+            self._type = value
+            return self
+        },
+        collection: function(value) {
+            self._type = value
+            return self
+        },
+        limit: function(value) {
+            self._limit = value
+            return self
+        },
+        cursor: function(value) {
+            self._cursor = value
+            return self
+        },
+        eq: function(key, value) {
+            query = self.andJoin(util.format('%s = %s', key, helpers.query.useQuotesIfRequired(value)))
+            return self
+        },
+        equal: this.eq,
+        gt: function(key, value) {
+            query = self.andJoin(util.format('%s > %s', key, helpers.query.useQuotesIfRequired(value)))
+            return self
+        },
+        greaterThan: this.gt,
+        gte: function(key, value) {
+            query = self.andJoin(util.format('%s >= %s', key, helpers.query.useQuotesIfRequired(value)))
+            return self
+        },
+        greaterThanOrEqual: this.gte,
+        lt: function(key, value) {
+            query = self.andJoin(util.format('%s < %s', key, helpers.query.useQuotesIfRequired(value)))
+            return self
+        },
+        lessThan: this.lt,
+        lte: function(key, value) {
+            query = self.andJoin(util.format('%s <= %s', key, helpers.query.useQuotesIfRequired(value)))
+            return self
+        },
+        lessThanOrEqual: this.lte,
+        contains: function(key, value) {
+            query = self.andJoin(util.format('%s contains %s', key, helpers.query.useQuotesIfRequired(value)))
+            return self
+        },
+        locationWithin: function(distanceInMeters, lat, lng) {
+            query = self.andJoin(util.format('location within %s of %s, %s', distanceInMeters, lat, lng))
+            return self
+        },
+        asc: function(key) {
+            self.sort(key, 'asc')
+            return self
+        },
+        desc: function(key) {
+            self.sort(key, 'desc')
+            return self
+        },
+        sort: function(key, order) {
+            sort = (key && order) ? util.format(' order by %s %s', key, order) : ''
+            return self
+        },
+        fromString: function(string) {
+            queryString = string
+            return self
+        },
+        andJoin: function(append) {
+            if (__nextIsNot) {
+                append = util.format("not %s", append)
+                __nextIsNot = false
+            }
+            if (!append) {
+                return query
+            } else if (query.length === 0) {
+                return append
+            } else {
+                return (_.endsWith(query, 'and') || _.endsWith(query, 'or')) ? util.format('%s %s', query, append) : util.format('%s and %s', query, append)
+            }
+        },
+        orJoin: function() {
+            return (query.length > 0 && !_.endsWith(query, 'or')) ? util.format('%s or', query) : query
+        }
+    })
+
+    // required properties
+    self._type = self._type || type
+
+    // public accessors
+    Object.defineProperty(self, '_ql', {
+        get: function() {
+            if (queryString !== undefined) {
+                return queryString
+            } else {
+                return util.format('select * %s %s', ((query.length > 0) ? 'where ' + (query || '') : ''),((sort !== undefined) ? sort : '')).trim()
+            }
+        }
+    })
+
+    Object.defineProperty(self, 'and', {
+        get: function() {
+            query = self.andJoin('')
+            return self
+        }
+    })
+
+    Object.defineProperty(self, 'or', {
+        get: function() {
+            query = self.orJoin()
+            return self
+        }
+    })
+
+    Object.defineProperty(self, 'not', {
+        get: function() {
+            __nextIsNot = true
+            return self
+        }
+    })
+
+    return self
+}
+
+module.exports = UsergridQuery
\ No newline at end of file
diff --git a/lib/request.js b/lib/request.js
new file mode 100644
index 0000000..77c88a5
--- /dev/null
+++ b/lib/request.js
@@ -0,0 +1,53 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var request = require('request'),
+    helpers = require('../helpers'),
+    UsergridResponse = require('../lib/response'),
+    _ = require('lodash')
+
+var UsergridRequest = function(options) {
+    var client = helpers.client.validate(options.client)
+    var callback = helpers.cb(helpers.args(arguments))
+
+    if (!_.isString(options.type) && !_.isString(options.path) && !_.isString(options.uri)) {
+        throw new Error('one of "type" (collection name), "path", or "uri" parameters are required when initializing a UsergridRequest')
+    }
+
+    if (!_.includes(['GET', 'PUT', 'POST', 'DELETE'], options.method)) {
+        throw new Error('"method" parameter is required when initializing a UsergridRequest')
+    }
+
+    var uri = options.uri || helpers.build.uri(client, options)
+    var formData = helpers.build.formData(options)
+    var body = ((formData !== undefined) ? undefined : options.body)
+
+    request(uri, {
+        headers: helpers.build.headers(client, options),
+        body: body,
+        encoding: options.encoding || null,
+        json: true,
+        method: options.method,
+        qs: helpers.build.qs(options),
+        formData: formData
+    }, function(error, response) {
+        var usergridResponse = new UsergridResponse(response)
+        var returnBody = _.first([usergridResponse.user, usergridResponse.users, usergridResponse.entity, usergridResponse.entities, usergridResponse.body].filter(_.isObject))
+        callback(error || usergridResponse.error, usergridResponse, returnBody)
+    })
+}
+
+module.exports = UsergridRequest
\ No newline at end of file
diff --git a/lib/response.js b/lib/response.js
new file mode 100644
index 0000000..9be05e0
--- /dev/null
+++ b/lib/response.js
@@ -0,0 +1,87 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var UsergridQuery = require('./query'),
+    UsergridResponseError = require('./responseError'),
+    helpers = require('../helpers'),
+    _ = require('lodash')
+
+var UsergridResponse = function(response) {
+    var self = this
+    self.ok = false
+    if (!response) {
+        return
+    } else if (response.statusCode < 400) {
+        self.ok = true
+        var UsergridEntity = require('./entity.js'),
+            UsergridUser = require('./user.js')
+        
+        _.assign(self, response)
+
+        if (_.has(response,'body.entities')) {
+            var entities = response.body.entities.map(function(en) {
+                var entity = new UsergridEntity(en)
+                if (entity.isUser) {
+                    entity = new UsergridUser(entity)
+                }
+                return entity
+            })
+            _.assign(self, {
+                metadata: _.cloneDeep(response.body),
+                entities: entities
+            })
+            delete self.metadata.entities
+            self.first = _.first(entities) || undefined
+            self.entity = self.first
+            self.last = _.last(entities) || undefined   
+            if (_.get(self,'metadata.path') === '/users') {
+                self.user = self.first
+                self.users = self.entities
+            }
+
+            Object.defineProperty(self, 'hasNextPage', {
+                get: function() {
+                    return _.has(self,'metadata.cursor')
+                }
+            })
+
+            helpers.setReadOnly(self.metadata)
+        }
+    } else {
+        _.assign(self, response, {
+            error: new UsergridResponseError(response.body)
+        })
+    }
+    return self;
+}
+
+UsergridResponse.prototype = {
+    loadNextPage: function() {
+        var args = helpers.args(arguments)
+        var callback = helpers.cb(args)
+        if (!this.metadata.cursor) {
+            callback()
+        }
+        var client = helpers.client.validate(args)
+        var type = _.last(_.get(this,'metadata.path').split('/'))
+        var limit = _.first(_.get(this,'metadata.params.limit'))
+        var ql =  _.first(_.get(this,'metadata.params.ql'))
+        var query = new UsergridQuery(type).fromString(ql).cursor(this.metadata.cursor).limit(limit)
+        return client.GET(query, callback)
+    }
+}
+
+module.exports = UsergridResponse
\ No newline at end of file
diff --git a/lib/responseError.js b/lib/responseError.js
new file mode 100644
index 0000000..4471bbd
--- /dev/null
+++ b/lib/responseError.js
@@ -0,0 +1,29 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var _ = require('lodash')
+
+var UsergridResponseError = function(responseErrorObject) {
+    if (_.get(responseErrorObject,'error') === false) {
+        return
+    }
+    this.name = responseErrorObject.error
+    this.description = responseErrorObject.error_description || responseErrorObject.description
+    this.exception = responseErrorObject.exception
+    return this
+}
+
+module.exports = UsergridResponseError
\ No newline at end of file
diff --git a/lib/user.js b/lib/user.js
new file mode 100644
index 0000000..90506b4
--- /dev/null
+++ b/lib/user.js
@@ -0,0 +1,159 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var UsergridEntity = require('./entity'),
+    UsergridQuery = require('./query'),
+    UsergridUserAuth = require('./userAuth'),
+    UsergridRequest = require('./request'),
+    UsergridClient = require('../lib/client'),
+    helpers = require('../helpers'),
+    util = require('util'),
+    _ = require('lodash')
+
+var UsergridUser = function(obj) {
+
+    if (!_.has(obj,'email') && !_.has(obj,'username')) {
+        // This is not a user entity
+        throw new Error('"email" or "username" property is required when initializing a UsergridUser object')
+    }
+
+    var self = this
+    self.type = "user"
+
+    _.assign(self, obj, UsergridEntity)
+    UsergridEntity.call(self, self)
+
+    helpers.setWritable(self, 'name')
+    return self
+}
+
+var CheckAvailable = function() {
+    var self = this
+    var args = helpers.args(arguments)
+    var client = helpers.client.validate(args)
+    if (args[0] instanceof UsergridClient) {
+        args.shift()
+    }
+    var callback = helpers.cb(args)
+    var checkQuery
+
+    if (args[0].username && args[0].email) {
+        checkQuery = new UsergridQuery('users').eq('username', args[0].username).or.eq('email', args[0].email)
+    } else if (args[0].username) {
+        checkQuery = new UsergridQuery('users').eq('username', args[0].username)
+    } else if (args[0].email) {
+        checkQuery = new UsergridQuery('users').eq('email', args[0].email)
+    } else {
+        throw new Error("'username' or 'email' property is required when checking for available users")
+    }
+
+    client.GET(checkQuery, function(error, usergridResponse) {
+        callback(error, usergridResponse, (usergridResponse.entities.length > 0))
+    }.bind(self))
+}
+
+UsergridUser.prototype = {
+    create: function() {
+        var self = this
+        var args = helpers.args(arguments)
+        var client = helpers.client.validate(args)
+        var callback = helpers.cb(args)
+        client.POST(self, function(error, usergridResponse) {
+            delete self.password
+            _.assign(self, usergridResponse.user)
+            callback(error, usergridResponse, usergridResponse.user)
+        }.bind(self))
+    },
+    login: function() {
+        var self = this
+        var args = helpers.args(arguments)
+        var callback = helpers.cb(args)
+        return new UsergridRequest({
+            client: helpers.client.validate(args),
+            path: 'token',
+            method: 'POST',
+            body: helpers.build.userLoginBody(self)
+        }, function(error, usergridResponse, body) {
+            delete self.password
+            if (usergridResponse.ok) {
+                self.auth = new UsergridUserAuth(body.user)
+                self.auth.token = body.access_token
+                self.auth.expiry = helpers.time.expiry(body.expires_in)
+                self.auth.tokenTtl = body.expires_in
+            }
+            callback(error, usergridResponse, body.access_token)
+        })
+    },
+    logout: function() {
+        var self = this
+        var args = helpers.args(arguments)
+        var callback = helpers.cb(args)
+        if (!self.auth || !self.auth.isValid) {
+            return callback({
+                name: 'no_valid_token',
+                description: 'this user does not have a valid token'
+            })
+        }
+
+        var revokeAll = _.first(args.filter(_.isBoolean)) || false
+
+        return new UsergridRequest({
+            client: helpers.client.validate(args),
+            path: util.format("users/%s/revoketoken%s", helpers.user.uniqueId(self), (revokeAll) ? "s" : ""),
+            method: 'PUT',
+            qs: (!revokeAll) ? {
+                token: self.auth.token
+            } : undefined
+        }, function(error, usergridResponse) {
+            self.auth.destroy()
+            callback(error, usergridResponse, usergridResponse.ok)
+        })
+    },
+    logoutAllSessions: function() {
+        var args = helpers.args(arguments)
+        args = _.concat([helpers.client.validate(args), true], args)
+        return this.logout.apply(this, args)
+    },
+    resetPassword: function() {
+        var self = this
+        var args = helpers.args(arguments)
+        var callback = helpers.cb(args)
+        var client = helpers.client.validate(args)
+        if (args[0] instanceof UsergridClient) {
+            args.shift()
+        }
+        var body = {
+            oldpassword: _.isPlainObject(args[0]) ? args[0].oldPassword : _.isString(args[0]) ? args[0] : undefined,
+            newpassword: _.isPlainObject(args[0]) ? args[0].newPassword : _.isString(args[1]) ? args[1] : undefined
+        }
+        if (!body.oldpassword || !body.newpassword) {
+            throw new Error('"oldPassword" and "newPassword" properties are required when resetting a user password')
+        }
+        return new UsergridRequest({
+            client: client,
+            path: util.format('users/%s/password', helpers.user.uniqueId(self)),
+            method: 'PUT',
+            body: body
+        }, function(error, usergridResponse) {
+            callback(error, usergridResponse, usergridResponse.ok)
+        })
+    }
+}
+
+util.inherits(UsergridUser, UsergridEntity)
+
+module.exports = UsergridUser
+module.exports.CheckAvailable = CheckAvailable
\ No newline at end of file
diff --git a/lib/userAuth.js b/lib/userAuth.js
new file mode 100644
index 0000000..5c61e5c
--- /dev/null
+++ b/lib/userAuth.js
@@ -0,0 +1,41 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var UsergridAuth = require('./auth'),
+    helpers = require('../helpers'),
+    util = require('util'),
+    _ = require('lodash')
+
+var UsergridUserAuth = function(options) {
+    var self = this
+    var args = _.flattenDeep(helpers.args(arguments))
+    if (_.isPlainObject(args[0])) {
+        options = args[0]
+    }
+    self.username = options.username || args[0]
+    self.email = options.email
+    if (options.password || args[1]) {
+        self.password = options.password || args[1]
+    }
+    self.tokenTtl = options.tokenTtl || args[2]
+    UsergridAuth.call(self)
+    _.assign(self, UsergridAuth)
+    return self
+}
+
+util.inherits(UsergridUserAuth, UsergridAuth)
+
+module.exports = UsergridUserAuth
\ No newline at end of file
diff --git a/package.json b/package.json
index 2d2579d..22011d9 100644
--- a/package.json
+++ b/package.json
@@ -1,43 +1,42 @@
 {
-  "name": "usergrid",
-  "version": "0.10.8",
-  "description": "A Node.js module for making API calls to App Services (Usergrid) from within Node.js",
-  "main": "./lib/usergrid.js",
+  "author": "Brandon Shelley",
+  "contributors": [{
+    "name": "Brandon Shelley",
+    "email": "brandon@codeblooded.io"
+  }, {
+    "name": "Robert Walsh",
+    "email": "rjwalsh1985@gmail.com"
+  }],
   "dependencies": {
-    "request": "2.12.x",
-    "inflection": "1.3.x"
+    "async": "latest",
+    "file": "latest",
+    "file-type": "^3.4.0",
+    "lodash": "~4.0",
+    "lodash-inflection": "latest",
+    "lodash-uuid": "latest",
+    "read-chunk": "^1.0.1",
+    "request": "latest",
+    "url-join": "latest",
+    "validator": "^4.5.0"
   },
+  "description": "The official Node.js SDK for Usergrid",
   "devDependencies": {
-    "mocha": "1.7.x",
-    "should": "1.2.1"
-  },
-  "scripts": {
-    "test": "mocha",
-    "start": "node example/server.js"
+    "chance": "^0.8.0",
+    "mocha": "latest",
+    "should": "latest"
   },
   "repository": {
     "type": "git",
-    "url": "git://github.com/apigee/usergrid-node-module.git"
+    "url": "git://github.com/brandonscript/usergrid-nodejs.git"
   },
-  "bugs": {
-    "url": "http://github.com/apigee/usergrid-node-module.git/issues"
+  "keywords": [],
+  "license": "Apache 2.0",
+  "main": "usergrid.js",
+  "name": "usergrid",
+  "private": false,
+  "scripts": {
+    "start": "node usergrid.js",
+    "test": "mocha tests"
   },
-  "keywords": [
-    "Node",
-    "Usergrid",
-    "Apigee",
-    "API"
-  ],
-  "tags": [
-    "usergrid",
-    "API",
-    "Apigee"
-  ],
-  "author": {
-    "name": "Rod Simpson",
-    "email": "rod@rodsimpson.com",
-    "url" : "http://rodsimpson.com"
-  },
-  "license": "Apache 2.0"
+  "version": "2.0.0-rc.2"
 }
-
diff --git a/readme.md b/readme.md
deleted file mode 100755
index 474d506..0000000
--- a/readme.md
+++ /dev/null
@@ -1,720 +0,0 @@
-# Usergrid Node.js SDK
-
-##Version
-
-Current Version: **0.10.8**
-
-See change log:
-
-<https://github.com/apache/usergrid-nodejs/blob/master/changelog.md>
-
-
-##Comments / Questions
-Please feel free to send comments or questions:
-
-	twitter: @rockerston
-	email: rockerston at apache.org
-
-Or just open github issues.  I truly want to know what you think, and will address all suggestions / comments / concerns.
-
-Thank you!
-
-Rod
-
-
-##Overview
-This Node.js module, which simplifies the process of making API calls to App Services from within Node.js, is available as an open-source project on github.  We welcome your contributions and suggestions. The repository is located here:
-
-<https://github.com/apache/usergrid-nodejs>
-
-
-##Client side Javascript
-Want to make calls to Usergrid client-side? No problem - just head over to the Usergrid Javascript SDK:
-
-<https://github.com/apache/usergrid-javascript>
-
-The syntax for this Node module and the Javascript SDK are almost exactly the same so you can easily transition between them.
-
-
-##Installing
-Use npm:
-
-	$ npm install usergrid
-
-
-##Getting started
-Include the module:
-
-	var usergrid = require('usergrid');
-
-Then create a new client:
-
-	var client = new usergrid.client({
-		orgName:'yourorgname',
-		appName:'sandbox',
-		logging: true, //optional - turn on logging, off by default
-	});
-
-The preceding example shows how to use the "Sandbox" testing app, which does not require any authentication.  The "Sandbox" comes with all new App Services accounts.
-
-If you are ready to use authentication, then create your client this way:
-
-	var client = new usergrid.client({
-		orgName:'yourorgname',
-		appName:'yourappname',
-		authType:usergrid.AUTH_CLIENT_ID,
-		clientId:'<your client id>',
-		clientSecret:'<your client secret>',
-		logging: false, //optional - turn on logging, off by default
-		buildCurl: false //optional - turn on curl commands, off by default
-	});
-
-The last two items are optional. The **logging** option will enable console.log output from the client.  The **buildCurl** option will cause cURL equivalent commands of all calls to the API to be displayed in the console.log output.
-
-You are now ready to use the usergrid handle to make calls against the API.
-
-
-##About the samples
-All of the samples provided in this readme file come from unit tests in the test.js which is located in the root of this project.
-
-
-To run the test file, first do the following:
-
-1. Change the org-name and app-name to point to your Usergrid organization account.  
-2. Change the client secret and client id
-
-Then run the code:
-
-	$ node test.js
-
-The samples in this file will show you the many ways you can use this module.
-
-
-##Entities and Collections
-Usergrid stores its data as "Entities" in "Collections".  Entities are essentially JSON objects and Collections are just like folders for storing these objects. You can learn more about Entities and Collections in the App Services docs:
-
-<http://usergrid.apache.org/docs/introduction/data-model.html>
-
-
-##Entities
-This module provides an easy way to make new entities. Here is a simple example that shows how to create a new object of type "dogs":
-
-	var options = {
-		type:'dogs',
-		name:'Dino'
-	}
-	client.createEntity(options, function (err, dog) {
-		if (err) {
-			//error - dog not created
-		} else {
-			//success -dog is created
-
-			//once the dog is created, you can set single properties:
-			dog.set('breed','Dinosaur');
-
-			//or a JSON object:
-			var data = {
-				master:'Fred',
-				state:'hungry'
-			}
-			//set is additive, so previously set properties are not overwritten
-			dog.set(data);
-
-			//finally, call save on the object to save it back to the database
-			dog.save(function(err){
-				if (err){
-					//error - dog not saved
-				} else {
-					//success - new dog is saved
-				}
-			});
-		}
-	});
-
-**note:** all calls to the API will be executed asynchronously, so it is important that you use a callback.
-
-
-You can also refresh the object from the database if needed (in case the data has been updated by a different client or device):
-
-	//call fetch to refresh the data from the server
-	dog.fetch(function(err){
-		if (err){
-			// error - dog not refreshed from database;
-		} else {
-			//dog has been refreshed from the database
-			//will only work if the UUID for the entity is in the dog object
-			//success - dog entity refreshed from database;
-		}
-	});
-
-To remove the entity from the database:
-
-	//the destroy method will delete the entity from the database
-	dog.destroy(function(err){
-		if (err){
-			//error - dog not removed from database
-		} else {
-			//success - dog removed from database (no real dogs were harmed!)
-			dog = null; //no real dogs were harmed!
-		}
-	});
-
-To set properties on the entity, use the set() method:
-
-	//once the dog is created, you can set single properties:
-	dog.set('breed','Dinosaur');
-
-	//or a JSON object:
-	var data = {
-		master:'Fred',
-		state:'hungry'
-	}
-	//set is additive, so previously set properties are not overwritten
-	dog.set(data);
-
-**Note:** These properties are now set locally, but make sure you call the .save() method on the entity to save them back to the database!
-
-To get a single property from the entity, use the get method:
-
-	var breed = dog.get('breed');
-
-or
-
-	var state = dog.get('state');
-
-or, to get a JSON object with all properties, don't pass a key
-
-	var props = dog.get();
-
-Based on the set statements above, our JSON object should look like this:
-
-	{
-		name:'Dino',
-		type:'dogs',
-		breed:'Dinosaur',
-		master:'Fred',
-		state:'hungry'
-	}
-
-**Wait!** But what if my entity already exists on the server?
-
-During a client.createEntity call, there are two ways that you can choose to handle this situation.  The question is, what should the client do if an entity with the same name, username, or uuid already exists on the server?
-
-  	1. Give you back an error.
-  	2. Give you back the pre-existing entity.
-
-If you want to get back an error when the entity already exists, then simply call the client.createEntity function as above. If there is a collision, you will get back a 400  However, if you want the existing entity to be returned, then set the getOnExist flag to true:
-
-	var options = {
-		type:'dogs',
-		name:'Dino',
-		getOnExist:true
-	}
-	client.createEntity(options, function (err, dog) {
-		if (err) {
-			//error - dog not created
-		} else {
-			//success -dog is created or returned, depending on if it already exists or not
-
-
-Alternatively, if you know that you only want to retrieve an existing entity, use the getEntity method:
-
-	var options = {
-		type:'users',
-		username:'marty'
-	}
-	client.getEntity(options, function(err, existingUser){
-		if (err){
-			//error - existing user not retrieved
-		} else {
-			//success - existing user was retrieved
-
-			var username = existingUser.get('username');
-		}
-	});
-
-
-##The Collection object
-The Collection object models Collections in the database.  Once you start programming your app, you will likely find that this is the most useful method of interacting with the database.  Creating a collection will automatically populate the object with entities from the collection. The following example shows how to create a Collection object, then how to use entities once the Collection has been populated with entities from the server:
-
-	//options object needs to have the type (which is the collection type)
-	var options = {
-		type:'dogs',
-		qs:{ql:'order by index'}
-	}
-
-	client.createCollection(options, function (err, dogs) {
-		if (err) {
-			//error - could not make collection
-		} else {
-
-			//success - new Collection worked
-
-			//we got the dogs, now display the Entities:
-			while(dogs.hasNextEntity()) {
-				//get a reference to the dog
-				dog = dogs.getNextEntity();
-				var name = dog.get('name');
-				notice('dog is called ' + name);
-			}
-
-			//success - looped through dogs
-
-		}
-	});
-
-
-You can also add a new entity of the same type to the collection:
-
-	//create a new dog and add it to the collection
-	var options = {
-		name:'extra-dog',
-		fur:'shedding'
-	}
-	//just pass the options to the addEntity method
-	//to the collection and it is saved automatically
-	dogs.addEntity(options, function(err, dog, data) {
-		if (err) {
-			//error - extra dog not saved or added to collection
-		} else {
-			//success - extra dog saved and added to collection
-		}
-	});
-
-
-##Collection iteration and paging
-The Collection object works in Pages of data.  This means that at any given time, the Collection object will have one page of data loaded.  You can iterate across all the entities in the current page of data by using the following pattern:
-
-	//we got the dogs, now display the Entities:
-	while(dogs.hasNextEntity()) {
-		//get a reference to the dog
-		dog = dogs.getNextEntity();
-		var name = dog.get('name');
-		notice('dog is called ' + name);
-	}
-
-To get the next page of data from the server, use the following pattern:
-
-	if (dogs.hasNextPage()) {
-		//there is a next page, so get it from the server
-		dogs.getNextPage(function(err){
-			if (err) {
-				//error - could not get next page of dogs
-			} else {
-				//success - got next page of dogs
-				//we got the dogs, now display the Entities:
-				while(dogs.hasNextEntity()) {
-					//get a reference to the dog
-					dog = dogs.getNextEntity();
-					var name = dog.get('name');
-					notice('dog is called ' + name);
-				}
-				//success - looped through dogs
-			}
-		});
-	}
-
-You can use the same pattern to get a previous page of data:
-
-	if (dogs.hasPreviousPage()) {
-		//there is a previous page, so get it from the server
-		dogs.getPreviousPage(function(err){
-			if(err) {
-				//error - could not get previous page of dogs
-			} else {
-				//success - got next page of dogs
-				//we got the dogs, now display the Entities:
-				while(dogs.hasNextEntity()) {
-					//get a reference to the dog
-					dog = dogs.getNextEntity();
-					var name = dog.get('name');
-					notice('dog is called ' + name);
-				}
-				//success - looped through dogs
-			}
-		});
-	}
-
-By default, the database will return 10 entities per page.  You can change that amount by setting a limit:
-
-
-	var options = {
-		type:'dogs',
-		qs:{limit:50} //limit statement set to 50
-	}
-
-	client.createCollection(options, function (err, dogs) {
-		if (err) {
-			//error - could not get all dogs
-		} else {
-			//success - got at most 50 dogs
-		}
-	}
-
-Several other convenience methods exist to make working with pages of data easier:
-
-* getFirstEntity - gets the first entity of a page
-* getLastEntity - gets the last entity of a page
-* resetEntityPointer - sets the internal pointer back to the first element of the page
-* getEntityByUUID - returns the entity if it is in the current page
-
-
-###Custom Queries
-A custom query allows you to tell the API that you want your results filtered or altered in some way.  To specify that the query results should be ordered by creation date, add the qs parameter to the options object:
-
-	var options = {
-		type:'dogs',
-		qs:{ql:'order by created DESC'}
-	};
-
-You may find that you need to change the query on an existing object.  Simply access the qs property directly:
-
-	dogs.qs = {ql:'order by created DESC'};
-
-
-If you also wanted to get more entities in the result set than the default 10, say 100, you can specify a query similar to the following (the limit can be a maximum of 999):
-
-	dogs.qs = {ql:'order by created DESC',limit:'100'};
-
-**Note**: there are many cases where expanding the result set is useful.  But be careful - the more results you get back in a single call, the longer it will take to transmit the data back to your app.
-
-Another common requirement is to limit the results to a specific query.  For example, to get all brown dogs, use the following syntax:
-
-	dogs.qs = {ql:"select * where color='brown'"};
-
-You can also limit the results returned such that only the fields you specify are returned:
-
-	dogs.qs = {'ql':"select name, age where color='brown'"};
-
-**Note:** in the two preceding examples that we put single quotes around 'brown', so it will be searched as a string.
-
-You can find more information on custom queries here:
-
-<http://usergrid.apache.org/docs/data-queries/querying-your-data.html>
-
-
-##Modeling users with the Entity object
-There is no specific User object in the module.  Instead, you simply need to use the Entity object, specifying a type of "users".  Here is an example:
-
-	//type is 'users', set additional paramaters as needed
-	var options = {
-		type:'users',
-		username:'marty',
-		password:'mysecurepassword',
-		name:'Marty McFly',
-		city:'Hill Valley'
-	}
-
-  	client.createEntity(options, function (err, marty) {
-		if (err){
-			//error - user not saved
-		} else {
-			//success - user saved
-		}
-	});
-
-
-If the user is modified, just call save on the user again:
-
-	//add properties cumulatively
-	marty.set('state', 'California');
-	marty.set("girlfriend","Jennifer");
-	marty.save(function(err){
-		if (err){
-			//error - user not updated
-		} else {
-			//success - user updated
-		}
-	});
-
-To refresh the user's information in the database:
-
-	marty.fetch(function(err){
-		if (err){
-			//error - not refreshed
-		} else {
-			//success - user refreshed
-		}
-	});
-
-If you no longer need the object, call the delete() method and the object will be deleted from database:
-
-	marty.destroy(function(err){
-		if (err){
-			//error - user not deleted from database
-		} else {
-			//success - user deleted from database
-			marty = null; //blow away the local object
-		}
-	});
-
-
-###Making connections
-Connections are a way to connect to entities with some verb.  This is called an entity relationship.  For example, if you have a user entity with username of marty, and a dog entity with a name of einstein, then using our RESTful API, you could make a call like this:
-
-	POST users/marty/likes/dogs/einstein
-
-This creates a one-way connection between marty and einstein, where marty "likes" einstein.
-
-Complete documentation on the entity relationships API can be found here:
-
-<http://usergrid.apache.org/docs/entity-connections/connecting-entities.html>
-
-The following code shows you how to create this connection, and then verify that the connection has been made:
-
-	marty.connect('likes', dog, function (err, data) {
-		if (err) {
-			// error - connection not created
-		} else {
-
-			//call succeeded, so pull the connections back down
-			marty.getConnections('likes', function (err, data) {
-				if (err) {
-						//error - could not get connections
-				} else {
-					//verify that connection exists
-					if (marty.likes.ralphy) {
-						//success - connection exists
-					} else {
-						//error - connection does not exist
-					}
-				}
-			});
-		}
-	});
-
-You can also remove connections, by using the disconnect method:
-
-	marty.disconnect('likes', dog, function (err, data) {
-		if (err) {
-			//error - connection not deleted
-		} else {
-
-			//call succeeded, so pull the connections back down
-			marty.getConnections('likes', function (err, data) {
-				if (err) {
-					//error - error getting connections
-				} else {
-					//verify that connection exists
-					if (marty.likes.einstein) {
-						//error - connection still exists
-					} else {
-						//success - connection deleted
-					}
-				}
-			});
-		}
-	});
-
-
-###To log a user in
-Up to this point, we have shown how you can use the client secret / client id combination to authenticate your calls against the API.  For a server-side Node.js app, this may be all you need.  However, if you do find that your app requires that you authenticate an individual user, you have several options.
-
-The first is to use client-side authentication with Ajax.  If you want to opt for this method, take a look at our Javascript SDK.  The syntax for usage is the same as this Node.js module, so it will be easy to pick up:
-
-<https://github.com/apache/usergrid-javascript>
-
-The other method is to log the user in server-side. When you log a user in, the API will return an OAuth token for you to use for calls to the API on the user's behalf.  Once that token is returned, you can either make a new client just for the user, or change the auth method on the existing client.  These methods are described below:
-
-
-	username = 'marty';
-	password = 'mysecurepassword';
-	client.login(username, password,
-		function (err) {
-			if (err) {
-				//error - could not log user in
-			} else {
-				//success - user has been logged in
-
-				//the login call will return an OAuth token, which is saved
-				//in the client object for later use.  Access it this way:
-				var token = client.token;
-
-				//then make a new client just for the app user, then use this
-				//client to make calls against the API
-				var appUserClient = new usergrid.client({
-					orgName:'yourorgname',
-					appName:'yourappname',
-					authType:usergrid.AUTH_APP_USER,
-					token:token
-				});
-
-				//alternitavely, you can change the authtype of the client:
-				client.authType = usergrid.AUTH_APP_USER;
-
-				//Then make calls against the API.  For example, you can
-				//get the user entity this way:
-				client.getLoggedInUser(function(err, data, user) {
-					if(err) {
-						//error - could not get logged in user
-					} else {
-						//success - got logged in user
-
-						//you can then get info from the user entity object:
-						var username = user.get('username');
-
-						//to log the user out, call the logout() method
-						appUserClient.logout();
-						client.logout();
-
-						//verify the logout worked
-						if (client.isLoggedIn()) {
-							//error - logout failed
-						} else {
-							//success - user has been logged out
-						}
-
-						//since we don't need to App User level calls anymore,
-						//set the authtype back to client:
-						client.authType = usergrid.AUTH_CLIENT_ID;
-
-						runner(step, marty);
-					}
-				});
-
-			}
-		}
-	);
-
-
-To recap, once a user has been logged in, and an OAuth token has been acquired, use one of the two methods to make calls to the API:
-
-1. Use the same client object and change auth types before each call
-
-2. Grab the token and make a new client object specifically for user calls.
-
-Either method will work.
-
-
-###To log a user out
-To log the user out, call:
-
-	client.logout();
-
-Or, if you made a new client object specifically for the app user:
-
-	appUserClient.logout();
-
-This destroys the token and user object in the client object, effectively logging the user out.
-
-##Groups
-This module provides an easy way to make new groups. They follow the same syntax as Entities
-
-##Making generic calls
-If you find that you need to make calls to the API that fall outside of the scope of the Entity and Collection objects, you can use the following format to make any REST calls against the API:
-
-	client.request(options, callback);
-
-This format allows you to make almost any call against the App Services (Usergrid) API. For example, to get a list of users:
-
-	var options = {
-		method:'GET',
-		endpoint:'users'
-	};
-	client.request(options, function (err, data) {
-		if (err) {
-			//error - GET failed
-		} else {
-			//data will contain raw results from API call
-			//success - GET worked
-		}
-	});
-
-Or, to create a new user:
-
-	var options = {
-		method:'POST',
-		endpoint:'users',
-		body:{ username:'fred', password:'secret' }
-	};
-	client.request(options, function (err, data) {
-		if (err) {
-			//error - POST failed
-		} else {
-			//data will contain raw results from API call
-			//success - POST worked
-		}
-	});
-
-Or, to update the new user:
-
-	var options = {
-		method:'PUT',
-		endpoint:'users/fred',
-		body:{ newkey:'newvalue' }
-	};
-	client.request(options, function (err, data) {
-		if (err) {
-			//error - PUT failed
-		} else {
-			//data will contain raw results from API call
-			//success - PUT worked
-		}
-	});
-
-Or to delete the new user:
-
-	var options = {
-		method:'DELETE',
-		endpoint:'users/fred'
-	};
-	client.request(options, function (err, data) {
-		if (err) {
-			//error - DELETE failed
-		} else {
-			//data will contain raw results from API call
-			//success - DELETE worked
-		}
-	});
-
-The Options Object for the client.request fuction:
-
-* `method` - http method (GET, POST, PUT, or DELETE), defaults to GET
-* `qs` - object containing querystring values to be appended to the uri
-* `body` - object containing entity body for POST and PUT requests
-* `endpoint` - API endpoint, for example "users/fred"
-* `mQuery` - boolean, set to true if running management query, defaults to false
-* `buildCurl` - boolean, set to true if you want to see equivalent curl commands in console.log, defaults to false
-
-You can make any call to the API using the format above.  However, in practice using the higher level Entity and Collection objects will make life easier as they take care of much of the heavy lifting.
-
-
-###cURL
-[cURL](http://curl.haxx.se/) is an excellent way to make calls directly against the API. As mentioned in the **Getting started** section of this guide, one of the parameters you can add to the new client options object is **buildCurl**:
-
-	var client = new Usergrid.Client({
-		orgName:'yourorgname',
-		appName:'sandbox',
-		logging: true, //optional - turn on logging, off by default
-		buildCurl: true //optional - turn on curl commands, off by default
-	});
-
-If you set this parameter to true, the SDK will build equivalent curl commands and send them to the console.log window.
-
-More information on cURL can be found here:
-
-<http://curl.haxx.se/>
-
-## Contributing
-We welcome your enhancements!
-
-Like [Usergrid](https://github.com/apache/usergrid-nodejs), the Usergrid Javascript SDK is open source and licensed under the Apache License, Version 2.0.
-
-1. Fork it
-2. Create your feature branch (`git checkout -b my-new-feature`)
-3. Commit your changes (`git commit -am 'Added some feature'`)
-4. Push your changes to the upstream branch (`git push origin my-new-feature`)
-5. Create new Pull Request (make sure you describe what you did and why your mod is needed)
-
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-<http://www.apache.org/licenses/LICENSE-2.0>
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
\ No newline at end of file
diff --git a/test.js b/test.js
deleted file mode 100755
index 354f8cb..0000000
--- a/test.js
+++ /dev/null
@@ -1,1077 +0,0 @@
-//
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-/**
-* Test suite for all the examples in the readme
-*
-* NOTE: No, this test suite doesn't use the traditional format for
-* a test suite.  This is because the goal is to require as little
-* alteration as possible during the copy / paste operation from this test
-* suite to the readme file.
-*
-* @author rod simpson (rod@apigee.com)
-*/
-
-
-var usergrid = require('./lib/usergrid');
-
-var logSuccess = true;
-var successCount = 0;
-var logError = true;
-var errorCount = 0;
-var logNotice = true;
-var _unique = new Date().getTime();
-var _username = 'marty'+_unique;
-var _email = 'marty'+_unique+'@timetravel.com';
-var _password = 'password2';
-var _newpassword = 'password3';
-
-var client = new usergrid.client({
-	orgName:'yourorgname',
-	appName:'sandbox',
-	logging: true, //optional - turn on logging, off by default
-	buildCurl: true //optional - turn on curl commands, off by default
-});
-
-
-//call the runner function to start the process
-client.logout();
-runner(0);
-
-function runner(step, arg, arg2){
-	step++;
-	switch(step)
-	{
-		case 1:
-			notice('-----running step '+step+': DELETE user from DB to prep test');
-			clearUser(step);
-			break;
-		case 2:
-			notice('-----running step '+step+': GET test');
-			testGET(step);
-			break;
-		case 3:
-			notice('-----running step '+step+': POST test');
-			testPOST(step);
-			break;
-		case 4:
-			notice('-----running step '+step+': PUT test');
-			testPUT(step);
-			break;
-		case 5:
-			notice('-----running step '+step+': DELETE test');
-			testDELETE(step);
-			break;
-		case 6:
-			notice('-----running step '+step+': prepare database - remove all dogs (no real dogs harmed here!!)');
-			cleanupAllDogs(step);
-			break;
-		case 7:
-			notice('-----running step '+step+': make a new dog');
-			makeNewDog(step);
-			break;
-		case 8:
-			notice('-----running step '+step+': update our dog');
-			updateDog(step, arg);
-			break;
-		case 9:
-			notice('-----running step '+step+': refresh our dog');
-			refreshDog(step, arg);
-			break;
-		case 10:
-			notice('-----running step '+step+': remove our dog from database (no real dogs harmed here!!)');
-			removeDogFromDatabase(step, arg);
-			break;
-		case 11:
-			notice('-----running step '+step+': make lots of dogs!');
-			makeSampleData(step, arg);
-			break;
-		case 12:
-			notice('-----running step '+step+': make a dogs collection and show each dog');
-			testDogsCollection(step);
-			break;
-		case 13:
-			notice('-----running step '+step+': get the next page of the dogs collection and show each dog');
-			getNextDogsPage(step, arg);
-			break;
-		case 14:
-			notice('-----running step '+step+': get the previous page of the dogs collection and show each dog');
-			getPreviousDogsPage(step, arg);
-			break;
-		case 15:
-			notice('-----running step '+step+': remove all dogs from the database (no real dogs harmed here!!)');
-			cleanupAllDogs(step);
-			break;
-		case 16:
-			notice('-----running step '+step+': prepare database (remove existing user if present)');
-			prepareDatabaseForNewUser(step);
-			break;
-		case 17:
-			notice('-----running step '+step+': create a new user');
-			createUser(step);
-			break;
-		case 18:
-			notice('-----running step '+step+': update the user');
-			updateUser(step, arg);
-			break;
-		case 19:
-			notice('-----running step '+step+': get the existing user');
-			getExistingUser(step, arg);
-			break;
-		case 20:
-			notice('-----running step '+step+': refresh the user from the database');
-			refreshUser(step, arg);
-			break;
-		case 21:
-			notice('-----running step '+step+': log user in');
-			loginUser(step, arg);
-			break;
-		case 22:
-			notice('-----running step '+step+': change users password');
-			changeUsersPassword(step, arg);
-			break;
-		case 23:
-			notice('-----running step '+step+': log user out');
-			logoutUser(step, arg);
-			break;
-		case 24:
-			notice('-----running step '+step+': relogin user');
-			reloginUser(step, arg);
-			break;
-		case 25:
-			notice('-----running step '+step+': logged in user creates dog');
-			createDog(step, arg);
-			break;
-		case 26:
-			notice('-----running step '+step+': logged in user likes dog');
-			userLikesDog(step, arg, arg2);
-			break;
-		case 27:
-			notice('-----running step '+step+': logged in user removes likes connection to dog');
-			removeUserLikesDog(step, arg, arg2);
-			break;
-		case 28:
-			notice('-----running step '+step+': user removes dog');
-			removeDog(step, arg, arg2);
-			break;
-		case 29:
-			notice('-----running step '+step+': log the user out');
-			logoutUser(step, arg);
-			break;
-		case 30:
-			notice('-----running step '+step+': remove the user from the database');
-			destroyUser(step, arg);
-			break;
-		case 31:
-			notice('-----running step '+step+': try to create existing entity');
-			createExistingEntity(step, arg);
-			break;
-		case 32:
-			notice('-----running step '+step+': try to create new entity with no name');
-			createNewEntityNoName(step, arg);
-			break;
-		case 33:
-			notice('-----running step '+step+': clean up users');
-      cleanUpUsers(step, arg);
-			break;
-		case 34:
-			notice('-----running step '+step+': clean up dogs');
-      cleanUpDogs(step, arg);
-			break;
-		case 35:
-			notice('-----running step '+step+': create counter');
-      counterCreate(step, arg);
-			break;
-		case 36:
-			notice('-----running step '+step+': reset counter');
-      counterReset(step, arg);
-			break;
-		case 37:
-			notice('-----running step '+step+': increment counter');
-      counterIncrement(step, arg);
-			break;
-		case 38:
-			notice('-----running step '+step+': decrement counter');
-      counterDecrement(step, arg);
-			break;
-		case 34:
-			notice('-----running step '+step+': fetch counter data');
-      counterFetch(step, arg);
-			break;
-		default:
-			notice('-----test complete!-----');
-			notice('Success count= ' + successCount);
-			notice('Error count= ' + errorCount);
-			notice('-----thank you for playing!-----');
-	}
-}
-
-//logging functions
-function success(message){
-	successCount++;
-	if (logSuccess) {
-		console.log('SUCCESS: ' + message);
-	}
-}
-
-function error(message){
-	errorCount++
-	if (logError) {
-		console.log('ERROR: ' + message);
-	}
-}
-
-function notice(message){
-	if (logNotice) {
-		console.log('NOTICE: ' + message);
-	}
-}
-
-//tests
-function clearUser(step) {
-  var options = {
-    method:'DELETE',
-    endpoint:'users/fred'
-  };
-  client.request(options, function (err, data) {
-    //data will contain raw results from API call
-    success('User cleared from DB');
-    runner(step);
-  });
-}
-
-function testGET(step) {
-	var options = {
-		method:'GET',
-		endpoint:'users'
-	};
-	client.request(options, function (err, data) {
-		if (err) {
-			error('GET failed');
-		} else {
-			//data will contain raw results from API call
-			success('GET worked');
-			runner(step);
-		}
-	});
-}
-
-function testPOST(step) {
-	var options = {
-		method:'POST',
-		endpoint:'users',
-		body:{ username:'fred', password:'secret' }
-	};
-	client.request(options, function (err, data) {
-		if (err) {
-			error('POST failed');
-		} else {
-			//data will contain raw results from API call
-			success('POST worked');
-			runner(step);
-		}
-	});
-}
-
-function testPUT(step) {
-	var options = {
-		method:'PUT',
-		endpoint:'users/fred',
-		body:{ newkey:'newvalue' }
-	};
-	client.request(options, function (err, data) {
-		if (err) {
-			error('PUT failed');
-		} else {
-			//data will contain raw results from API call
-			success('PUT worked');
-			runner(step);
-		}
-	});
-}
-
-function testDELETE(step) {
-	var options = {
-		method:'DELETE',
-		endpoint:'users/fred'
-	};
-	client.request(options, function (err, data) {
-		if (err) {
-			error('DELETE failed');
-		} else {
-			//data will contain raw results from API call
-			success('DELETE worked');
-			runner(step);
-		}
-	});
-}
-
-function makeNewDog(step) {
-
-	var options = {
-		type:'dogs',
-		name:'Ralph'+_unique
-	}
-
-	client.createEntity(options, function (err, dog) {
-		if (err) {
-			error('dog not created');
-		} else {
-			success('dog is created');
-
-			//once the dog is created, you can set single properties:
-			dog.set('breed','Dinosaur');
-
-			//the set function can also take a JSON object:
-			var data = {
-				master:'Fred',
-				state:'hungry'
-			}
-			//set is additive, so previously set properties are not overwritten
-			dog.set(data);
-
-			//finally, call save on the object to save it back to the database
-			dog.save(function(err){
-				if (err){
-					error('dog not saved');
-				} else {
-					success('new dog is saved');
-					runner(step, dog);
-				}
-			});
-		}
-	});
-
-}
-
-function updateDog(step, dog) {
-
-	//change a property in the object
-	dog.set("state", "fed");
-	//and save back to the database
-	dog.save(function(err){
-		if (err){
-			error('dog not saved');
-		} else {
-			success('dog is saved');
-			runner(step, dog);
-		}
-	});
-
-}
-
-function refreshDog(step, dog){
-
-	//call fetch to refresh the data from the server
-	dog.fetch(function(err){
-		if (err){
-			error('dog not refreshed from database');
-		} else {
-			//dog has been refreshed from the database
-			//will only work if the UUID for the entity is in the dog object
-			success('dog entity refreshed from database');
-			runner(step, dog);
-		}
-	});
-
-}
-
-function removeDogFromDatabase(step, dog){
-
-	//the destroy method will delete the entity from the database
-	dog.destroy(function(err){
-		if (err){
-			error('dog not removed from database');
-		} else {
-			success('dog removed from database'); // no real dogs were harmed!
-			dog = null; //no real dogs were harmed!
-			runner(step, 1);
-		}
-	});
-
-}
-
-function makeSampleData(step, i) {
-	notice('making dog '+i);
-
-	var options = {
-		type:'dogs',
-		name:'dog'+_unique+i,
-		index:i
-	}
-
-	client.createEntity(options, function (err, dog) {
-		if (err) {
-			error('dog ' + i + ' not created');
-		} else {
-			if (i >= 30) {
-				//data made, ready to go
-				success('all dogs made');
-				runner(step);
-			} else {
-				success('dog ' + i + ' made');
-				//keep making dogs
-				makeSampleData(step, ++i);
-			}
-		}
-	});
-}
-
-function testDogsCollection(step) {
-
-	var options = {
-		type:'dogs',
-		qs:{ql:'order by index'}
-	}
-
-	client.createCollection(options, function (err, dogs) {
-		if (err) {
-			error('could not make collection');
-		} else {
-
-			success('new Collection created');
-
-			//we got the dogs, now display the Entities:
-			while(dogs.hasNextEntity()) {
-				//get a reference to the dog
-				dog = dogs.getNextEntity();
-				var name = dog.get('name');
-				notice('dog is called ' + name);
-			}
-
-			success('looped through dogs');
-
-			//create a new dog and add it to the collection
-			var options = {
-				name:'extra-dog',
-				fur:'shedding'
-			}
-			//just pass the options to the addEntity method
-			//to the collection and it is saved automatically
-			dogs.addEntity(options, function(err, dog, data) {
-				if (err) {
-					error('extra dog not saved or added to collection');
-				} else {
-					success('extra dog saved and added to collection');
-					runner(step, dogs);
-				}
-			});
-		}
-	});
-
-}
-
-function getNextDogsPage(step, dogs) {
-
-	if (dogs.hasNextPage()) {
-		//there is a next page, so get it from the server
-		dogs.getNextPage(function(err){
-			if (err) {
-				error('could not get next page of dogs');
-			} else {
-				success('got next page of dogs');
-				//we got the next page of data, so do something with it:
-				var i = 11;
-				while(dogs.hasNextEntity()) {
-					//get a reference to the dog
-					var dog = dogs.getNextEntity();
-					var index = dog.get('index');
-					if(i !== index) {
-						error('wrong dog loaded: wanted' + i + ', got ' + index);
-					}
-					notice('got dog ' + i);
-					i++
-				}
-				success('looped through dogs')
-				runner(step, dogs);
-			}
-		});
-	} else {
-		getPreviousDogsPage(dogs);
-	}
-
-}
-
-function getPreviousDogsPage(step, dogs) {
-
-	if (dogs.hasPreviousPage()) {
-		//there is a previous page, so get it from the server
-		dogs.getPreviousPage(function(err){
-			if(err) {
-				error('could not get previous page of dogs');
-			} else {
-				success('got next page of dogs');
-				//we got the previous page of data, so do something with it:
-				var i = 1;
-				while(dogs.hasNextEntity()) {
-					//get a reference to the dog
-					var dog = dogs.getNextEntity();
-					var index = dog.get('index');
-					if(i !== index) {
-						error('wrong dog loaded: wanted' + i + ', got ' + index);
-					}
-					notice('got dog ' + i);
-					i++
-				}
-				success('looped through dogs');
-				runner(step);
-			}
-		});
-	} else {
-		getAllDogs();
-	}
-}
-
-function cleanupAllDogs(step){
-
-	var options = {
-		type:'dogs',
-		qs:{limit:50} //limit statement set to 50
-	}
-
-	client.createCollection(options, function (err, dogs) {
-		if (err) {
-			error('could not get all dogs');
-		} else {
-			success('got at most 50 dogs');
-			//we got 50 dogs, now display the Entities:
-			while(dogs.hasNextEntity()) {
-				//get a reference to the dog
-				var dog = dogs.getNextEntity();
-				var name = dog.get('name');
-				notice('dog is called ' + name);
-			}
-			dogs.resetEntityPointer();
-			//do doggy cleanup
-			while(dogs.hasNextEntity()) {
-				//get a reference to the dog
-				var dog = dogs.getNextEntity();
-				var dogname = dog.get('name');
-				notice('removing dog ' + dogname + ' from database');
-				dog.destroy(function(err, data) {
-					if (err) {
-						error('dog not removed');
-					} else {
-						success('dog removed');
-					}
-				});
-			}
-
-			//no need to wait around for dogs to be removed, so go on to next test
-			runner(step);
-		}
-	});
-}
-
-
-function prepareDatabaseForNewUser(step) {
-	var options = {
-		method:'DELETE',
-		endpoint:'users/',
-    qs:{ql:"select * where username ='marty*'"}
-	};
-	client.request(options, function (err, data) {
-		if (err) {
-			notice('database ready - no user to delete');
-		runner(step);
-		} else {
-			//data will contain raw results from API call
-			success('database ready - user deleted worked');
-			runner(step);
-		}
-	});
-}
-
-function createUser(step) {
-	client.signup(_username, _password, _email, 'Marty McFly',
-		function (err, marty) {
-			if (err){
-				error('user not created');
-				runner(step, marty);
-			} else {
-				success('user created');
-				runner(step, marty);
-			}
-		}
-	);
-}
-
-function updateUser(step, marty) {
-
-	//add properties cumulatively
-	marty.set('state', 'California');
-	marty.set("girlfriend","Jennifer");
-	marty.save(function(err){
-		if (err){
-			error('user not updated');
-		} else {
-			success('user updated');
-			runner(step, marty);
-		}
-	});
-
-}
-
-function getExistingUser(step, marty) {
-
-	var options = {
-		type:'users',
-		username:_username
-	}
-	client.getEntity(options, function(err, existingUser){
-		if (err){
-			error('existing user not retrieved');
-		} else {
-			success('existing user was retrieved');
-
-			var username = existingUser.get('username');
-			if (username === _username){
-				success('got existing user username');
-			} else {
-				error('could not get existing user username');
-			}
-			runner(step, marty);
-		}
-	});
-
-}
-
-
-function refreshUser(step, marty) {
-
-	marty.fetch(function(err){
-		if (err){
-			error('not refreshed');
-		} else {
-			success('user refreshed');
-			runner(step, marty);
-		}
-	});
-
-}
-
-function loginUser(step, marty) {
-	username = _username;
-	password = _password;
-	client.login(username, password,
-		function (err) {
-			if (err) {
-				error('could not log user in');
-			} else {
-				success('user has been logged in');
-
-				//the login call will return an OAuth token, which is saved
-				//in the client object for later use.  Access it this way:
-				var token = client.token;
-
-				//then make a new client just for the app user, then use this
-				//client to make calls against the API
-				var appUserClient = new usergrid.client({
-					orgName:'yourorgname',
-					appName:'yourappname',
-					authType:usergrid.AUTH_APP_USER,
-					token:token
-				});
-
-				//alternitavely, you can change the authtype of the client:
-				client.authType = usergrid.AUTH_APP_USER;
-
-				//Then make calls against the API.  For example, you can
-				//get the user entity this way:
-				client.getLoggedInUser(function(err, data, user) {
-					if(err) {
-						error('could not get logged in user');
-					} else {
-						success('got logged in user');
-
-						//you can then get info from the user entity object:
-						var username = user.get('username');
-						notice('logged in user was: ' + username);
-
-						runner(step, user);
-					}
-				});
-
-			}
-		}
-	);
-}
-
-function changeUsersPassword(step, marty) {
-
-	marty.set('oldpassword', _password);
-	marty.set('newpassword', _newpassword);
-	marty.save(function(err){
-		if (err){
-			error('user password not updated');
-		} else {
-			success('user password updated');
-			runner(step, marty);
-		}
-	});
-
-}
-
-function logoutUser(step, marty) {
-
-	//to log the user out, call the logout() method
-	client.logout();
-
-	//verify the logout worked
-	if (client.isLoggedIn()) {
-		error('logout failed');
-	} else {
-		success('user has been logged out');
-	}
-
-	runner(step, marty);
-}
-
-function reloginUser(step, marty) {
-
-	username = _username
-	password = _newpassword;
-	client.login(username, password,
-		function (err) {
-		if (err) {
-			error('could not relog user in');
-		} else {
-			success('user has been re-logged in');
-			runner(step, marty);
-		}
-		}
-	);
-}
-
-
-
-//TODO: currently, this code assumes permissions have been set to support user actions.  need to add code to show how to add new role and permission programatically
-//
-//first create a new permission on the default role:
-//POST "https://api.usergrid.com/yourorgname/yourappname/roles/default/permissions" -d '{"permission":"get,post,put,delete:/dogs/**"}'
-//then after user actions, delete the permission on the default role:
-//DELETE "https://api.usergrid.com/yourorgname/yourappname/roles/default/permissions?permission=get%2Cpost%2Cput%2Cdelete%3A%2Fdogs%2F**"
-
-
-function createDog(step, marty) {
-  //see if marty can create a new dog now that he is logged in
-
-	var options = {
-		type:'dogs',
-		name:'einstein',
-		breed:'mutt'
-	}
-
-	client.createEntity(options, function (err, dog) {
-		if (err) {
-			error('POST new dog by logged in user failed');
-		} else {
-			success('POST new dog by logged in user succeeded');
-			runner(step, marty, dog);
-		}
-	});
-
-}
-
-function userLikesDog(step, marty, dog) {
-
-	marty.connect('likes', dog, function (err, data) {
-		if (err) {
-			error('connection not created');
-			runner(step, marty);
-		} else {
-
-			//call succeeded, so pull the connections back down
-			marty.getConnections('likes', function (err, data) {
-				if (err) {
-						error('could not get connections');
-				} else {
-					//verify that connection exists
-					if (marty.likes.einstein) {
-						success('connection exists');
-					} else {
-						error('connection does not exist');
-					}
-
-					runner(step, marty, dog);
-				}
-			});
-		}
-	});
-
-}
-
-function removeUserLikesDog(step, marty, dog) {
-
-	marty.disconnect('likes', dog, function (err, data) {
-		if (err) {
-			error('connection not deleted');
-			runner(step, marty);
-		} else {
-
-			//call succeeded, so pull the connections back down
-			marty.getConnections('likes', function (err, data) {
-				if (err) {
-					error('error getting connections');
-				} else {
-					//verify that connection exists
-					if (marty.likes.einstein) {
-						error('connection still exists');
-					} else {
-						success('connection deleted');
-					}
-
-					runner(step, marty, dog);
-				}
-			});
-		}
-	});
-
-}
-
-function removeDog(step, marty, dog) {
-
-	//now delete the dog from the database
-	dog.destroy(function(err, data) {
-		if (err) {
-			error('dog not removed');
-		} else {
-			success('dog removed');
-		}
-	});
-	runner(step, marty);
-}
-
-function destroyUser(step, marty) {
-
-	marty.destroy(function(err){
-		if (err){
-			error('user not deleted from database');
-		} else {
-			success('user deleted from database');
-			marty = null; //blow away the local object
-			runner(step);
-		}
-	});
-
-}
-
-function createExistingEntity(step, marty) {
-
-	var options = {
-		type:'dogs',
-		name:'einstein'
-	}
-
-	client.createEntity(options, function (err, dog) {
-		if (err) {
-			error('Create new entity to use for existing entity failed');
-		} else {
-			success('Create new entity to use for existing entity succeeded');
-
-			var uuid = dog.get('uuid');
-			//now create new entity, but use same entity name of einstein.  This means that
-			//the original einstein entity now exists.  Thus, the new einstein entity should
-			//be the same as the original + any data differences from the options var:
-
-			options = {
-				type:'dogs',
-				name:'einstein',
-				breed:'mutt'
-			}
-			client.createEntity(options, function (err, newdog) {
-				if (err) {
-					error('Create new entity to use for existing entity failed');
-				} else {
-					success('Create new entity to use for existing entity succeeded');
-
-					var newuuid = newdog.get('uuid');
-					if (newuuid === uuid) {
-						success('UUIDs of new and old entities match');
-					} else {
-						error('UUIDs of new and old entities do not match');
-					}
-
-					var breed = newdog.get('breed');
-					if (breed === 'mutt') {
-						success('attribute sucesfully set on new entity');
-					} else {
-						error('attribute not sucesfully set on new entity');
-					}
-
-					newdog.destroy(function(err){
-						if (err){
-							error('existing entity not deleted from database');
-						} else {
-							success('existing entity deleted from database');
-							dog = null; //blow away the local object
-							newdog = null; //blow away the local object
-							runner(step);
-						}
-					});
-
-				}
-			});
-		}
-	});
-
-}
-
-function createNewEntityNoName(step, marty) {
-
-	var options = {
-   type:"something",
-   othervalue:"something else"
-	}
-
-	client.createEntity(options, function (err, entity) {
-		if (err) {
-			error('Create new entity with no name failed');
-		} else {
-			success('Create new entity with no name succeeded');
-
-      entity.destroy();
-      runner(step);
-		}
-	});
-
-}
-
-function cleanUpUsers(step){
-
-  var options = {
-    type:'users',
-    qs:{limit:50} //limit statement set to 50
-  }
-
-  client.createCollection(options, function (err, users) {
-    if (err) {
-      error('could not get all users');
-    } else {
-      success('got users');
-      //do doggy cleanup
-      while(users.hasNextEntity()) {
-        //get a reference to the dog
-        var user = users.getNextEntity();
-        var username = user.get('name');
-        notice('removing dog ' + username + ' from database');
-        user.destroy(function(err, data) {
-          if (err) {
-            error('user not removed');
-          } else {
-            success('user removed');
-          }
-        });
-      }
-
-      runner(step);
-    }
-  });
-
-}
-
-function cleanUpDogs(step){
-
-    var options = {
-      type:'dogs',
-      qs:{limit:50} //limit statement set to 50
-    }
-
-    client.createCollection(options, function (err, dogs) {
-      if (err) {
-        error('could not get all dogs');
-      } else {
-        success('got at most 50 dogs');
-        //we got 50 dogs, now display the Entities:
-        while(dogs.hasNextEntity()) {
-          //get a reference to the dog
-          var dog = dogs.getNextEntity();
-          var name = dog.get('name');
-          notice('dog is called ' + name);
-        }
-        dogs.resetEntityPointer();
-        //do doggy cleanup
-        while(dogs.hasNextEntity()) {
-          //get a reference to the dog
-          var dog = dogs.getNextEntity();
-          var dogname = dog.get('name');
-          notice('removing dog ' + dogname + ' from database');
-          dog.destroy(function(err, data) {
-            if (err) {
-              error('dog not removed');
-            } else {
-              success('dog removed');
-            }
-          });
-        }
-
-        //no need to wait around for dogs to be removed, so go on to next test
-        runner(step);
-      }
-    });
-  }
-//var counter;
-function counterCreate(step){
-	var counter = new usergrid.counter({client:client, data:{category:'mocha_test', timestamp:0, name:"test", counters:{test:0,test_counter:0}}}, function(err, data){
-        if (err) {
-          error('counter not removed');
-        } else {
-          success('counter created');
-        }
-	});
-	runner(step, counter);
-}
-function counterReset(step, counter){
-	counter.reset({name:'test'}, function(err, data){
-        if (err) {
-          error('counter not reset');
-        } else {
-          success('counter reset');
-        }
-		runner(step, counter);
-	});
-}
-function counterIncrement(step, counter){
-	counter.increment({name:'test', value:1}, function(err, data){
-        if (err) {
-          error('counter not incremented');
-        } else {
-          success('counter incremented');
-        }
-		runner(step, counter);
-	});
-}
-function counterDecrement(step, counter){
-	counter.decrement({name:'test', value:1}, function(err, data){
-        if (err) {
-          error('counter not decremented');
-        } else {
-          success('counter decremented');
-        }
-		runner(step, counter);
-	});
-}
-function counterFetch(step, counter){
-	counter.getData({resolution:'all', counters:['test', 'test_counter']}, function(err, data){
-        if (err) {
-          error('counter not fetched');
-        } else {
-          success('counter fetched');
-        }
-		runner(step, counter);
-	});
-}
diff --git a/test/client.js b/test/client.js
deleted file mode 100755
index 70f8d85..0000000
--- a/test/client.js
+++ /dev/null
@@ -1,183 +0,0 @@
-//
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-
-/**
-* Test suite for Client object
-*
-* @author rod simpson (rod@apigee.com)
-*/
-//require('assert');
-require('should');
-var usergrid = require('../lib/usergrid.js');
-
-//first set up the client
-var myclient = new usergrid.client(
-  {
-    orgName:"1hotrod"
-  , appName:"sandbox"
-  , authType:"CLIENT_ID"
-  , clientId:"b3U6y6hRJufDEeGW9hIxOwbREg"
-  , clientSecret:"b3U6X__fN2l9vd1HVi1kM9nJvgc-h5k"
-  }
-);
-
-describe('Standard Requests', function(){
-  describe('DELETE Method', function(){
-    it('should DELETE without error', function(done){
-      myclient.request(
-        { method:"DELETE"
-        , endpoint:"users/aaaaaa"
-        } ,done);
-    });
-  });
-  describe('cURL DELETE Method', function(){
-    it('should create a valid cURL calll for the DELETE without error', function(){
-      var options = {
-        method:"DELETE"
-        , uri:"https://api.usergrid.com/1hotrod/sandbox/users/aaaaaa"
-      }
-      var curl = myclient.buildCurlCall(options);
-      curl.should.equal('curl -X DELETE https://api.usergrid.com/1hotrod/sandbox/users/aaaaaa');
-    });
-  });
-
-
-  describe('POST Method', function(){
-    it('should POST without error', function(done){
-      myclient.request(
-        { method:"POST"
-        , endpoint:"users"
-        , body:{'username':'aaaaaa', 'password':'abcd1234'}
-        } ,done);
-    })
-  });
-  describe('cURL POST Method', function(){
-    it('should create a valid cURL calll for the POST without error', function(){
-      var options = {
-        method:"POST"
-        , uri:"https://api.usergrid.com/1hotrod/sandbox/users"
-        , body:{'username':'aaaaaa', 'password':'abcd1234'}
-      }
-      var curl = myclient.buildCurlCall(options);
-      curl.should.equal("curl -X POST https://api.usergrid.com/1hotrod/sandbox/users -d '{\"username\":\"aaaaaa\",\"password\":\"abcd1234\"}'");
-    });
-  });
-
-
-  describe('PUT Method', function(){
-    it('should PUT without error', function(done){
-      myclient.request(
-        { method:"PUT"
-        , endpoint:"users/aaaaaa"
-        , body:{'fred':'value'}
-        } ,done);
-    });
-  });
-  describe('cURL PUT Method', function(){
-    it('should create a valid cURL calll for the PUT without error', function(){
-      var options = {
-        method:"PUT"
-        , uri:"https://api.usergrid.com/1hotrod/sandbox/users"
-        , body:{'fred':'value'}
-      }
-      var curl = myclient.buildCurlCall(options);
-      curl.should.equal("curl -X PUT https://api.usergrid.com/1hotrod/sandbox/users -d '{\"fred\":\"value\"}'");
-    });
-  });
-
-
-  describe('GET Method', function(){
-    it('should GET without error', function(done){
-      myclient.request(
-        { method:"GET"
-        , endpoint:"users/aaaaaa"
-        } ,done);
-    });
-  });
-  describe('cURL GET Method', function(){
-    it('should create a valid cURL calll for the GET without error', function(){
-      var options = {
-        method:"GET"
-        , uri:"https://api.usergrid.com/1hotrod/sandbox/users/aaaaaa"
-      }
-      var curl = myclient.buildCurlCall(options);
-      curl.should.equal('curl -X GET https://api.usergrid.com/1hotrod/sandbox/users/aaaaaa');
-    });
-  });
-
-  describe('Login Method', function(){
-    it('should Login without error and get token', function(done){
-      myclient.login('aaaaaa', 'abcd1234', function(err){
-        if (err) throw err;
-
-        //test the token first
-        var token = myclient.token;
-        myclient.should.have.property('token');
-
-        //make sure we get a user back
-        var user = myclient.user;
-        var data = user.get();
-        data.should.have.property('username');
-
-        //test for logged in user
-        if (!myclient.isLoggedIn()) throw err;
-
-        //make a query with the app users token
-        myclient.authType = usergrid.APP_USER;
-
-        //do a get on /users
-        describe('GET Method', function(){
-          it('should GET without error', function(done){
-            myclient.request(
-              { method:"GET"
-              , endpoint:"users"
-              } ,done);
-          });
-        });
-
-        //go back to the
-        myclient.authType = usergrid.AUTH_CLIENT_ID;
-
-        //erase the token
-        myclient.token = null;
-        if (myclient.isLoggedIn()) throw err;
-
-        //reset the token
-        myclient.token = token;
-        if (!myclient.isLoggedIn()) throw err;
-
-        //clear the logged in user
-        myclient.user = null;
-        if (myclient.isLoggedIn()) throw err;
-
-        //replace the logged in user
-        myclient.user = user;
-        if (!myclient.isLoggedIn()) throw err;
-
-        //log the user out
-        myclient.logout();
-        if (myclient.isLoggedIn()) throw err;
-
-        //tests finished
-        done();
-      });
-    });
-  })
-
-});
-
diff --git a/test/collection.js b/test/collection.js
deleted file mode 100755
index 1a8eadb..0000000
--- a/test/collection.js
+++ /dev/null
@@ -1,259 +0,0 @@
-//
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-/**
-* Test suite for Collection object
-*
-* TODO: need to add coverage for the following methods:
-*
-* getFirstEntity
-* getLastEntity
-* hasPrevEntity
-* getPrevEntity
-* resetEntityPointer
-* resetPaging
-*
-* Need to add sample data for paging, check actual results
-*
-* @author rod simpson (rod@apigee.com)
-*/
-require("assert");
-require('should');
-var usergrid = require('../lib/usergrid.js');
-
-//first set up the client
-var myclient = new usergrid.client({
-  orgName:'1hotrod',
-  appName:'sandbox',
-  authType:'CLIENT_ID',
-  clientId:'b3U6y6hRJufDEeGW9hIxOwbREg',
-  clientSecret:'b3U6X__fN2l9vd1HVi1kM9nJvgc-h5k',
-  logging: true
-});
-
-describe('Collection methods - dogs', function(){
-  var doggies = {};
-
-  describe('make new collection', function(){
-    it('should make a new collection without error', function(done){
-      var options = {
-        client:myclient,
-        path:"dogs"
-      }
-      doggies = new usergrid.collection(options, done);
-    });
-  });
-
-  describe('check collection', function(){
-    it('should loop through all collection entities', function(){
-      while(doggies.hasNextEntity()) {
-        //get a reference to the dog
-        var dog = doggies.getNextEntity();
-        var data = dog.get();
-        data.should.have.property('name');
-      }
-    });
-  });
-});
-
-describe('Collection methods - users', function(){
-  var users = {};
-
-  describe('make new collection', function(){
-    it('should make a new collection without error', function(done){
-      var options = {
-        client:myclient,
-        path:'users'
-      }
-      users = new usergrid.collection(options, done);
-    });
-  });
-
-  describe('check collection', function(){
-    it('should loop through all collection entities', function(){
-      while(users.hasNextEntity()) {
-        //get a reference to the dog
-        var user = users.getNextEntity();
-        var data = user.get();
-        data.should.have.property('username');
-      }
-    });
-  });
-});
-
-describe('Collection methods - 1 user - barney', function(){
-  var users = {};
-  var uuid = '';
-  var user_barney = {};
-
-  describe('make new collection', function(){
-    it('should make a new collection without error', function(done){
-      var options = {
-        client:myclient,
-        path:'users',
-        qs:{"ql":"select * where username ='barney'"}
-      }
-      users = new usergrid.collection(options, done);
-    });
-  });
-
-  describe('check collection', function(){
-    it('should loop through all collection entities', function(){
-      while(users.hasNextEntity()) {
-        //get a reference to the dog
-        var user = users.getNextEntity();
-        var data = user.get();
-        data.should.have.property('username', 'barney');
-      }
-    });
-  });
-
-  describe('Add 1 user to collection', function(){
-    it('should make a new user and add it to collection without error', function(done){
-      //first delete the user if he exists (no assertion as the data may or may not be there)
-      myclient.request({
-        method:'DELETE',
-        endpoint:'users/fredflintster'
-        }, function(err) {
-          /// new entity creation
-          var data = {
-          	type:'users',
-            username: 'fredflintster',
-            password: 'barney',
-            email: 'email@myemail.com'
-          };
-          var options = {
-            client:myclient,
-            data:data
-          };
-          user_barney = new Entity(options);
-          users.addEntity(user_barney, done);
-        });
-    });
-  });
-
-  describe('Get 1 user from collection', function(){
-    it('should return user without error', function(done){
-      //make sure we get the uuid from barney
-      var data = user_barney.get();
-      data.should.have.property('uuid');
-
-      var uuid =  user_barney.get('uuid');
-      users.getEntityByUUID(uuid, function(err, data, user) {
-        user_barney = user;
-        var data = user_barney.get();
-        data.should.have.property('uuid');
-        uuid = user_barney.get('uuid');
-        done();
-      });
-    });
-  });
-
-  describe('remove entity from collection', function(){
-    it('should remove entity from collection without error', function(done){
-      users.destroyEntity(user_barney, done);
-    });
-  });
-
-});
-
-
-
-
-
-var messageeClient = new usergrid.client({
-  orgName:'apigee',
-  appName:'messageeapp',
-  authType:'CLIENT_ID',
-  clientId:'YXA6URHEY2pCEeG23RIxOAoChA',
-  clientSecret:'YXA6ukLeZvwB0JOdmAprY1azi9DtCPY',
-  logging: true
-});
-
-describe('Collection methods - users paging', function(){
-  var users = {};
-
-  describe('make new collection', function(){
-    it('should make a new collection without error', function(done){
-      var options = {
-        path:'users',
-        client:messageeClient
-      }
-      users = new usergrid.collection(options, done);
-    });
-  });
-
-  describe('check collection', function(){
-    it('should loop through all collection entities', function(){
-      while(users.hasNextEntity()) {
-        //get a reference to the dog
-        var user = users.getNextEntity();
-        var data = user.get();
-        data.should.have.property('username');
-        console.log(data.username);
-      }
-    });
-  });
-
-  describe('get next page', function(){
-    it('should get next page of users', function(done){
-      console.log('starting next page test');
-      if (users.hasNextPage()) {
-        console.log('next page - yes');
-        users.getNextPage(done);
-      } else {
-        done();
-      }
-    });
-  });
-
-  describe('check collection', function(){
-    it('should loop through all collection entities', function(){
-      while(users.hasNextEntity()) {
-        //get a reference to the dog
-        var user = users.getNextEntity();
-        var data = user.get();
-        data.should.have.property('username');
-        console.log(data.username);
-      }
-    });
-  });
-
-  describe('get previous page', function(){
-    it('should get previous page of users', function(done){
-      if (users.hasPreviousPage()) {
-        users.getPreviousPage(done);
-      } else {
-        done();
-      }
-    });
-  });
-
-  describe('check collection', function(){
-    it('should loop through all collection entities', function(){
-      while(users.hasNextEntity()) {
-        //get a reference to the dog
-        var user = users.getNextEntity();
-        var data = user.get();
-        data.should.have.property('username');
-        console.log(data.username);
-      }
-    });
-  });
-
-});
-
diff --git a/test/entity.js b/test/entity.js
deleted file mode 100755
index 3cd3b2e..0000000
--- a/test/entity.js
+++ /dev/null
@@ -1,59 +0,0 @@
-//
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-/**
-* Test suite for Entity object
-*
-* Run with mocha v. 1.7.x
-* http://visionmedia.github.com/mocha/
-*
-* @author rod simpson (rod@apigee.com)
-*/
-require("assert");
-var usergrid = require('../lib/usergrid.js');
-
-//first set up the client
-var client = new usergrid.client({
-  orgName:'1hotrod',
-  appName:'sandbox',
-  authType:'CLIENT_ID',
-  clientId:'b3U6y6hRJufDEeGW9hIxOwbREg',
-  clientSecret:'b3U6X__fN2l9vd1HVi1kM9nJvgc-h5k'
-});
-
-describe('Entity methods', function(){
-  var dog = new usergrid.entity({
-    client:client,
-    data:{type:"dogs"}
-  });
-  describe('save method', function(){
-    it('should save without error', function(done){
-      dog.set('name','dougy');
-      dog.save(done);
-    });
-  });
-  describe('fetch method', function(){
-    it('should fetch without error', function(done){
-      dog.fetch(done);
-    });
-  });
-  describe('destroy method', function(){
-    it('should destroy without error', function(done){
-      dog.destroy(done);
-    });
-  });
-});
\ No newline at end of file
diff --git a/tests/config.test.json b/tests/config.test.json
new file mode 100644
index 0000000..654298e
--- /dev/null
+++ b/tests/config.test.json
@@ -0,0 +1,28 @@
+{
+    "1.0": {
+        "orgId": "rwalsh",
+        "appId": "nodejs",
+        "baseUrl": "https://api.usergrid.com",
+        "clientId": "YXA68wzo4KbAEea4p6tiytxmag",
+        "clientSecret": "YXA671McJDKY_C3oU1HpdbJBqGMf_Y0",
+        "test": {
+            "collection": "nodejs",
+            "email": "authtest@test.com",
+            "password": "P@ssw0rd",
+            "username": "authtest"
+        }
+    },
+    "2.1": {
+        "appId": "sdksandbox",
+        "baseUrl": "https://api-connectors-prod.apigee.net/appservices",
+        "clientId": "YXA6WMhAuFJTEeWoggrRE9kXrQ",
+        "clientSecret": "YXA6zZbat7PKgOlN73rpByc36LWaUhw",
+        "orgId": "api-connectors",
+        "test": {
+            "collection": "nodejs",
+            "email": "authtest@test.com",
+            "password": "P@ssw0rd",
+            "username": "authtest"
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/lib/asset.test.js b/tests/lib/asset.test.js
new file mode 100644
index 0000000..389a616
--- /dev/null
+++ b/tests/lib/asset.test.js
@@ -0,0 +1,118 @@
+'use strict'
+
+var config = require('../../helpers').config,
+    UsergridEntity = require('../../lib/entity'),
+    UsergridAsset = require('../../lib/asset'),
+    UsergridClient = require('../../lib/client'),
+    util = require('util'),
+    fs = require('fs')
+
+var _slow = 6000,
+    _timeout = 12000,
+    filename = 'old_man',
+    file = __dirname + '/image.jpg',
+    testFile = __dirname + '/image_test.jpg',
+    expectedContentLength = 109055
+
+describe('init from fs.readFile()', function() {
+    var asset = new UsergridAsset(filename, file)
+
+    before(function(done) {
+        fs.readFile(file, function(err, data) {
+            asset.data = data
+            done()
+        })
+    })
+
+    it('asset.data should be a binary Buffer', function() {
+        asset.data.should.be.a.buffer()
+    })
+
+    it('asset.contentType should be inferred from Buffer', function() {
+        asset.contentType.should.equal('image/jpeg')
+    })
+
+    it(util.format('asset.contentLength should be %s bytes', expectedContentLength), function() {
+        asset.contentLength.should.equal(expectedContentLength)
+    })
+})
+
+describe('init from piped writable stream', function() {
+    var asset = new UsergridAsset(filename, file)
+    var writeTestAsset = new UsergridAsset('image_test', testFile)
+    before(function(done) {
+        var stream = fs.createReadStream(file).pipe(asset),
+            writeTest
+        stream.on('finish', function() {
+            fs.writeFile(testFile, asset.data)
+            writeTest = fs.createReadStream(file).pipe(writeTestAsset)
+            writeTest.on('finish', function() {
+                done()
+            })
+        })
+    })
+
+    it('asset.data should be a binary Buffer', function() {
+        asset.data.should.be.a.buffer()
+    })
+
+    it('asset.contentType should be inferred from Buffer', function() {
+        asset.contentType.should.equal('image/jpeg')
+    })
+
+    it(util.format('asset.contentLength should be %s bytes', expectedContentLength), function() {
+        asset.contentLength.should.equal(expectedContentLength)
+    })
+
+    it('should write an identical asset to the filesystem', function() {
+        writeTestAsset.contentType.should.equal('image/jpeg')
+        writeTestAsset.contentLength.should.equal(expectedContentLength)
+    })
+})
+
+describe('upload via client.POST to a specific entity', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    var client = new UsergridClient()
+    it('should upload a binary asset and create a new entity', function(done) {
+        var asset = new UsergridAsset(filename, file)
+        fs.createReadStream(file).pipe(asset).on('finish', function() {
+            client.POST(config.test.collection, asset, function(err, assetResponse, entityWithAsset) {
+                assetResponse.statusCode.should.equal(200)
+                entityWithAsset.should.have.property('file-metadata')
+                entityWithAsset['file-metadata'].should.have.property('content-type').equal('image/jpeg')
+                entityWithAsset['file-metadata'].should.have.property('content-length').equal(expectedContentLength)
+                entityWithAsset.remove(client)
+                done()
+            })
+        })
+    })
+})
+
+describe('upload via client.PUT to a specific entity', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    var client = new UsergridClient()
+    it('should upload a binary asset to an existing entity', function(done) {
+        var entity = new UsergridEntity({
+            type: config.test.collection,
+            name: "AssetTestPUT"
+        })
+        var asset = new UsergridAsset(filename, file)
+        client.PUT(entity, function(err, entityResponse, createdEntity) {
+            fs.createReadStream(file).pipe(asset).on('finish', function() {
+                client.PUT(createdEntity, asset, function(err, assetResponse, entityWithAsset) {
+                    assetResponse.statusCode.should.equal(200)
+                    entityWithAsset.should.have.property('file-metadata')
+                    entityWithAsset['file-metadata'].should.have.property('content-type').equal('image/jpeg')
+                    entityWithAsset['file-metadata'].should.have.property('content-length').equal(expectedContentLength)
+                    done()
+                })
+            })
+        })
+    })
+})
\ No newline at end of file
diff --git a/tests/lib/client.auth.test.js b/tests/lib/client.auth.test.js
new file mode 100644
index 0000000..67567de
--- /dev/null
+++ b/tests/lib/client.auth.test.js
@@ -0,0 +1,350 @@
+'use strict'
+
+var should = require('should'),
+    chance = new require('chance').Chance(),
+    util = require('util'),
+    config = require('../../helpers').config,
+    UsergridClient = require('../../lib/client'),
+    UsergridAuth = require('../../lib/auth'),
+    UsergridAppAuth = require('../../lib/appAuth'),
+    UsergridUserAuth = require('../../lib/userAuth'),
+    UsergridUser = require('../../lib/user')
+
+var _slow = 500,
+    _timeout = 4000
+
+describe('authMode', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    var token, client = new UsergridClient()
+    before(function(done) {
+        // authenticate app and remove sandbox permissions
+        client.setAppAuth(config.clientId, config.clientSecret)
+        client.authenticateApp(function(e, r, t) {
+            token = t
+            client.usingAuth(client.appAuth).DELETE('roles/guest/permissions', {
+                permission: "get,post,put,delete:/**"
+            }, function() {
+                done()
+            })
+        })
+    })
+
+    it('should fall back to using no authentication when currentUser is not authenticated and authMode is set to NONE', function(done) {
+        client.authMode = UsergridAuth.AUTH_MODE_NONE
+        client.GET('users', function(error, usergridResponse) {
+            should(client.currentUser).be.undefined()
+            usergridResponse.request.headers.should.not.have.property('authorization')
+            error.name.should.equal('unauthorized')
+            usergridResponse.ok.should.be.false()
+            done()
+        })
+    })
+
+    it('should fall back to using the app token when currentUser is not authenticated and authMode is set to APP', function(done) {
+        client.authMode = UsergridAuth.AUTH_MODE_APP
+        client.GET('users', function(error, usergridResponse, user) {
+            should(client.currentUser).be.undefined()
+            usergridResponse.request.headers.should.have.property('authorization').equal(util.format('Bearer %s', token))
+            usergridResponse.ok.should.be.true()
+            user.should.be.an.instanceof(UsergridUser)
+            done()
+        })
+    })
+
+    after(function(done) {
+        // re-add sandbox permissions
+        client.authMode = UsergridAuth.AUTH_MODE_NONE
+        client.usingAuth(client.appAuth).POST('roles/guest/permissions', {
+            permission: "get,post,put,delete:/**"
+        }, function() {
+            done()
+        })
+    })
+})
+
+describe('authenticateApp()', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    var response, token, client = new UsergridClient()
+    before(function(done) {
+        client.setAppAuth(config.clientId, config.clientSecret)
+        client.authenticateApp(function(err, r, t) {
+            response = r
+            token = t
+            done()
+        })
+    })
+
+    it('response.ok should be true', function() {
+        response.ok.should.be.true()
+    })
+
+    it('should have a valid token', function() {
+        token.should.be.a.String()
+        token.length.should.be.greaterThan(10)
+    })
+
+    it('client.appAuth.token should be set to the token returned from Usergrid', function() {
+        client.appAuth.should.have.property('token').equal(token)
+    })
+
+    it('client.appAuth.isValid should be true', function() {
+        client.appAuth.should.have.property('isValid').which.is.true()
+    })
+
+    it('client.appAuth.expiry should be set to a future date', function() {
+        client.appAuth.should.have.property('expiry').greaterThan(Date.now())
+    })
+
+    it('should fail when called without a clientId and clientSecret', function() {
+        should(function() {
+            var client = new UsergridClient()
+            client.setAppAuth(undefined, undefined, 0)
+            client.authenticateApp()
+        }).throw()
+    })
+
+    it('should authenticate by passing clientId and clientSecret in an object', function(done) {
+        var isolatedClient = new UsergridClient()
+        isolatedClient.authenticateApp(config, function(err, reponse, token) {
+            isolatedClient.appAuth.should.have.property('token').equal(token)
+            done()
+        })
+    })
+
+    it('should authenticate by passing a UsergridAppAuth instance with a custom ttl', function(done) {
+        var isolatedClient = new UsergridClient()
+        var ttlInMilliseconds = 500000
+        var appAuth = new UsergridAppAuth(config.clientId, config.clientSecret, ttlInMilliseconds)
+        isolatedClient.authenticateApp(appAuth, function(err, response, token) {
+            isolatedClient.appAuth.should.have.property('token').equal(token)
+            response.body.expires_in.should.equal(ttlInMilliseconds / 1000)
+            done()
+        })
+    })
+
+    it('should not set client.appAuth when authenticating with a bad clientId and clientSecret in an object', function(done) {
+        var failClient = new UsergridClient()
+        failClient.appAuth = undefined
+        failClient.authenticateApp(new UsergridAppAuth('BADCLIENTID', 'BADCLIENTSECRET'), function(e, r, token) {
+            e.should.containDeep({
+                name: 'invalid_grant',
+                description: 'invalid username or password'
+            })
+            should(token).be.undefined()
+            should(failClient.appAuth).be.undefined()
+            done()
+        })
+    })
+
+    it('should not set client.appAuth when authenticating with a bad UsergridAppAuth instance (using an object)', function(done) {
+        var failClient = new UsergridClient()
+        failClient.appAuth = undefined
+        failClient.authenticateApp(new UsergridAppAuth('BADCLIENTID', 'BADCLIENTSECRET'), function(e, r, token) {
+            e.should.containDeep({
+                name: 'invalid_grant',
+                description: 'invalid username or password'
+            })
+            should(token).be.undefined()
+            should(failClient.appAuth).be.undefined()
+            done()
+        })
+    })
+
+
+    it('should not set client.appAuth when authenticating with a bad UsergridAppAuth instance (using arguments)', function(done) {
+        var failClient = new UsergridClient()
+        failClient.appAuth = undefined
+        failClient.authenticateApp(new UsergridAppAuth('BADCLIENTID', 'BADCLIENTSECRET'), function(e, r, token) {
+            e.should.containDeep({
+                name: 'invalid_grant',
+                description: 'invalid username or password'
+            })
+            should(token).be.undefined()
+            should(failClient.appAuth).be.undefined()
+            done()
+        })
+    })
+})
+
+describe('authenticateUser()', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    var response, token, email = util.format("%s@%s.com", chance.word(), chance.word()),
+        client = new UsergridClient()
+    before(function(done) {
+        client.authenticateUser({
+            username: config.test.username,
+            password: config.test.password,
+            email: email
+        }, function(err, r, t) {
+            response = r
+            token = t
+            done()
+        })
+    })
+
+    it('should fail when called without a email (or username) and password', function() {
+        should(function() {
+            var badClient = new UsergridClient()
+            badClient.authenticateUser({})
+        }).throw()
+    })
+
+    it('response.ok should be true', function() {
+        response.ok.should.be.true()
+    })
+
+    it('should have a valid token', function() {
+        token.should.be.a.String()
+        token.length.should.be.greaterThan(10)
+    })
+
+    it('client.currentUser.auth.token should be set to the token returned from Usergrid', function() {
+        client.currentUser.auth.should.have.property('token').equal(token)
+    })
+
+    it('client.currentUser.auth.isValid should be true', function() {
+        client.currentUser.auth.should.have.property('isValid').which.is.true()
+    })
+
+    it('client.currentUser.auth.expiry should be set to a future date', function() {
+        client.currentUser.auth.should.have.property('expiry').greaterThan(Date.now())
+    })
+
+    it('client.currentUser should have a username and email', function() {
+        client.currentUser.should.have.property('username')
+        client.currentUser.should.have.property('email').equal(email)
+    })
+
+    it('client.currentUser and client.currentUser.auth should not store password', function() {
+        client.currentUser.should.not.have.property('password')
+        client.currentUser.auth.should.not.have.property('password')
+    })
+
+    it('should support an optional bool to not set as current user', function(done) {
+        var noCurrentUserClient = new UsergridClient()
+        noCurrentUserClient.authenticateUser({
+            username: config.test.username,
+            password: config.test.password,
+            email: email
+        }, false, function() {
+            should(noCurrentUserClient.currentUser).be.undefined()
+            done()
+        })
+    })
+
+    it('should support passing a UsergridUserAuth instance with a custom ttl', function(done) {
+        var newClient = new UsergridClient()
+        var ttlInMilliseconds = 500000
+        var userAuth = new UsergridUserAuth(config.test.username, config.test.password, ttlInMilliseconds)
+        newClient.authenticateUser(userAuth, function(err, usergridResponse, token) {
+            usergridResponse.ok.should.be.true()
+            newClient.currentUser.auth.token.should.equal(token)
+            usergridResponse.body.expires_in.should.equal(ttlInMilliseconds / 1000)
+            done()
+        })
+    })
+})
+
+describe('appAuth, setAppAuth()', function() {
+    it('should initialize by passing a list of arguments', function() {
+        var client = new UsergridClient()
+        client.setAppAuth(config.clientId, config.clientSecret, config.tokenTtl)
+        client.appAuth.should.be.instanceof(UsergridAppAuth)
+    })
+
+    it('should be a subclass of UsergridAuth', function() {
+        var client = new UsergridClient()
+        client.setAppAuth(config.clientId, config.clientSecret, config.tokenTtl)
+        client.appAuth.should.be.instanceof(UsergridAuth)
+    })
+
+    it('should initialize by passing an object', function() {
+        var client = new UsergridClient()
+        client.setAppAuth({
+            clientId: config.clientId,
+            clientSecret: config.clientSecret,
+            tokenTtl: config.tokenTtl
+        })
+        client.appAuth.should.be.instanceof(UsergridAppAuth)
+    })
+
+    it('should initialize by passing an instance of UsergridAppAuth', function() {
+        var client = new UsergridClient()
+        client.setAppAuth(new UsergridAppAuth(config.clientId, config.clientSecret, config.tokenTtl))
+        client.appAuth.should.be.instanceof(UsergridAppAuth)
+    })
+
+    it('should initialize by setting to an instance of UsergridAppAuth', function() {
+        var client = new UsergridClient()
+        client.appAuth = new UsergridAppAuth(config.clientId, config.clientSecret, config.tokenTtl)
+        client.appAuth.should.be.instanceof(UsergridAppAuth)
+    })
+})
+
+describe('usingAuth()', function() {
+
+    this.slow(_slow + 500)
+    this.timeout(_timeout)
+
+    var client = new UsergridClient(),
+        authFromToken
+
+    before(function(done) {
+        client.authenticateUser({
+            username: config.test.username,
+            password: config.test.password
+        }, function(error, response, token) {
+            authFromToken = new UsergridAuth(token)
+            done()
+        })
+    })
+
+    it('should authenticate using an ad-hoc token', function(done) {
+        authFromToken.isValid.should.be.true()
+        authFromToken.should.have.property('token')
+        client.usingAuth(authFromToken).GET({
+            path: '/users/me'
+        }, function(error, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            usergridResponse.should.have.property('user').which.is.an.instanceof(UsergridUser)
+            usergridResponse.user.should.have.property('uuid').which.is.a.uuid()
+            done()
+        })
+    })
+
+    it('client.tempAuth should be destroyed after making a request with ad-hoc authentication', function(done) {
+        should(client.tempAuth).be.undefined()
+        done()
+    })
+
+    it('should send an unauthenticated request when UsergridAuth.NO_AUTH is passed to .usingAuth()', function(done) {
+        client.usingAuth(UsergridAuth.NO_AUTH).GET({
+            path: '/users/me'
+        }, function(error, usergridResponse) {
+            usergridResponse.ok.should.be.false()
+            usergridResponse.request.headers.should.not.have.property('authentication')
+            usergridResponse.should.not.have.property('user')
+            done()
+        })
+    })
+
+    it('should send an unauthenticated request when no arguments are passed to .usingAuth()', function(done) {
+        client.usingAuth().GET({
+            path: '/users/me'
+        }, function(error, usergridResponse) {
+            usergridResponse.ok.should.be.false()
+            usergridResponse.request.headers.should.not.have.property('authentication')
+            usergridResponse.should.not.have.property('user')
+            done()
+        })
+    })
+})
\ No newline at end of file
diff --git a/tests/lib/client.connections.test.js b/tests/lib/client.connections.test.js
new file mode 100644
index 0000000..22ed45f
--- /dev/null
+++ b/tests/lib/client.connections.test.js
@@ -0,0 +1,294 @@
+'use strict'
+
+var should = require('should'),
+    urljoin = require('url-join'),
+    config = require('../../helpers').config,
+    UsergridClient = require('../../lib/client'),
+    UsergridQuery = require('../../lib/query')
+
+var _slow = 500,
+    _timeout = 4000
+
+describe('connect()', function() {
+
+    this.slow(_slow + 1000)
+    this.timeout(_timeout + 4000)
+
+    var response,
+        entity1,
+        entity2,
+        client = new UsergridClient(),
+        query = new UsergridQuery(config.test.collection).eq('name', 'testClientConnectOne').or.eq('name', 'testClientConnectTwo').asc('name')
+
+    before(function(done) {
+        // Create the entities we're going to use for connections
+        client.POST(config.test.collection, [{
+            "name": "testClientConnectOne"
+        }, {
+            "name": "testClientConnectTwo"
+        }], function() {
+            client.GET(query, function(err, usergridResponse) {
+                response = usergridResponse
+                entity1 = response.first
+                entity2 = response.last
+                done()
+            })
+        })
+    })
+
+    it('should connect entities by passing UsergridEntity objects as parameters', function(done) {
+        var relationship = "foos"
+
+        client.connect(entity1, relationship, entity2, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, relationship, function(err, usergridResponse) {
+                usergridResponse.first.metadata.connecting[relationship].should.equal(urljoin(
+                    "",
+                    config.test.collection,
+                    entity1.uuid,
+                    relationship,
+                    entity2.uuid,
+                    "connecting",
+                    relationship
+                ))
+                done()
+            })
+        })
+    })
+
+    it('should connect entities by passing a source UsergridEntity object and a target uuid', function(done) {
+        var relationship = "bars"
+
+        client.connect(entity1, relationship, entity2.uuid, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, relationship, function(err, usergridResponse) {
+                usergridResponse.first.metadata.connecting[relationship].should.equal(urljoin(
+                    "",
+                    config.test.collection,
+                    entity1.uuid,
+                    relationship,
+                    entity2.uuid,
+                    "connecting",
+                    relationship
+                ))
+                done()
+            })
+        })
+    })
+
+    it('should connect entities by passing source type, source uuid, and target uuid as parameters', function(done) {
+        var relationship = "bazzes"
+
+        client.connect(entity1.type, entity1.uuid, relationship, entity2.uuid, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, relationship, function(err, usergridResponse) {
+                usergridResponse.first.metadata.connecting[relationship].should.equal(urljoin(
+                    "",
+                    config.test.collection,
+                    entity1.uuid,
+                    relationship,
+                    entity2.uuid,
+                    "connecting",
+                    relationship
+                ))
+                done()
+            })
+        })
+    })
+
+    it('should connect entities by passing source type, source name, target type, and target name as parameters', function(done) {
+        var relationship = "quxes"
+
+        client.connect(entity1.type, entity1.name, relationship, entity2.type, entity2.name, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, relationship, function(err, usergridResponse) {
+                usergridResponse.first.metadata.connecting[relationship].should.equal(urljoin(
+                    "",
+                    config.test.collection,
+                    entity1.uuid,
+                    relationship,
+                    entity2.uuid,
+                    "connecting",
+                    relationship
+                ))
+                done()
+            })
+        })
+    })
+
+    it('should connect entities by passing a preconfigured options object', function(done) {
+        var options = {
+            entity: entity1,
+            relationship: "quuxes",
+            to: entity2
+        }
+
+        client.connect(options, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, options.relationship, function(err, usergridResponse) {
+                usergridResponse.first.metadata.connecting[options.relationship].should.equal(urljoin(
+                    "",
+                    config.test.collection,
+                    entity1.uuid,
+                    options.relationship,
+                    entity2.uuid,
+                    "connecting",
+                    options.relationship
+                ))
+                done()
+            })
+        })
+    })
+
+    it('should fail to connect entities when specifying target name without type', function() {
+        should(function() {
+            client.connect(entity1.type, entity1.name, "fails", 'badName', function() {})
+        }).throw()
+    })
+})
+
+describe('getConnections()', function() {
+
+    this.slow(_slow + 1000)
+    this.timeout(_timeout + 4000)
+
+    var response,
+        client = new UsergridClient(),
+        query = new UsergridQuery(config.test.collection).eq('name', 'testClientConnectOne').or.eq('name', 'testClientConnectTwo').asc('name')
+
+    before(function(done) {
+        client.GET(query, function(err, usergridResponse) {
+            response = usergridResponse
+            done()
+        })
+    })
+
+    it('should get an entity\'s outbound connections', function(done) {
+        var entity1 = response.first
+        var entity2 = response.last
+
+        var relationship = "foos"
+
+        client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, relationship, function(err, usergridResponse) {
+            usergridResponse.first.metadata.connecting[relationship].should.equal(urljoin(
+                "",
+                config.test.collection,
+                entity1.uuid,
+                relationship,
+                entity2.uuid,
+                "connecting",
+                relationship
+            ))
+            done()
+        })
+    })
+
+    it('should get an entity\'s inbound connections', function(done) {
+        var entity1 = response.first
+        var entity2 = response.last
+
+        var relationship = "foos"
+
+        client.getConnections(UsergridClient.Connections.DIRECTION_IN, entity2, relationship, function(err, usergridResponse) {
+            usergridResponse.first.metadata.connections[relationship].should.equal(urljoin(
+                "",
+                config.test.collection,
+                entity2.uuid,
+                "connecting",
+                entity1.uuid,
+                relationship
+            ))
+            done()
+        })
+    })
+})
+
+describe('disconnect()', function() {
+
+    this.slow(_slow + 1000)
+    this.timeout(_timeout + 4000)
+
+    var response,
+        client = new UsergridClient(),
+        query = new UsergridQuery(config.test.collection).eq('name', 'testClientConnectOne').or.eq('name', 'testClientConnectTwo').asc('name')
+
+    before(function(done) {
+        client.GET(query, function(err, usergridResponse) {
+            response = usergridResponse
+            done()
+        })
+    })
+
+    it('should disconnect entities by passing UsergridEntity objects as parameters', function(done) {
+        var entity1 = response.first
+        var entity2 = response.last
+
+        var relationship = "foos"
+
+        client.disconnect(entity1, relationship, entity2, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, relationship, function(err, usergridResponse) {
+                usergridResponse.entities.should.be.an.Array().with.lengthOf(0)
+                done()
+            })
+        })
+    })
+
+    it('should disconnect entities by passing source type, source uuid, and target uuid as parameters', function(done) {
+        var entity1 = response.first
+        var entity2 = response.last
+
+        var relationship = "bars"
+
+        client.disconnect(entity1.type, entity1.uuid, relationship, entity2.uuid, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, relationship, function(err, usergridResponse) {
+                usergridResponse.entities.should.be.an.Array().with.lengthOf(0)
+                done()
+            })
+        })
+    })
+
+    it('should disconnect entities by passing source type, source name, target type, and target name as parameters', function(done) {
+        var entity1 = response.first
+        var entity2 = response.last
+
+        var relationship = "bazzes"
+
+        client.disconnect(entity1.type, entity1.name, relationship, entity2.type, entity2.name, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, relationship, function(err, usergridResponse) {
+                usergridResponse.entities.should.be.an.Array().with.lengthOf(0)
+                done()
+            })
+        })
+    })
+
+    it('should disconnect entities by passing a preconfigured options object', function(done) {
+        var entity1 = response.first
+        var entity2 = response.last
+
+        var options = {
+            entity: entity1,
+            relationship: "quxes",
+            to: entity2
+        }
+
+        client.disconnect(options, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, options.relationship, function(err, usergridResponse) {
+                usergridResponse.entities.should.be.an.Array().with.lengthOf(0)
+                done()
+            })
+        })
+    })
+
+    it('should fail to disconnect entities when specifying target name without type', function() {
+        var entity1 = response.first
+        var entity2 = response.last
+
+        should(function() {
+            client.disconnect(entity1.type, entity1.name, "fails", entity2.name, function() {})
+        }).throw()
+    })
+})
\ No newline at end of file
diff --git a/tests/lib/client.init.test.js b/tests/lib/client.init.test.js
new file mode 100644
index 0000000..01702bd
--- /dev/null
+++ b/tests/lib/client.init.test.js
@@ -0,0 +1,37 @@
+'use strict'
+
+var should = require('should'),
+    config = require('../../helpers').config,
+    UsergridClient = require('../../lib/client')
+
+describe('initialization', function() {
+    it('should fail to initialize without an orgId and appId', function() {
+        should(function() {
+            var client = new UsergridClient(null, null)
+            client.GET()
+        }).throw()
+    })
+
+    it('should initialize using properties defined in config.json', function() {
+        var client = new UsergridClient()
+        client.should.be.an.instanceof(UsergridClient).with.property('orgId').equal(config.orgId)
+        client.should.have.property('appId').equal(config.appId)
+    })
+
+    it('should initialize when passing orgId and appId as arguments, taking precedence over config', function() {
+        var client = new UsergridClient('foo', 'bar')
+        client.should.be.an.instanceof(UsergridClient).with.property('orgId').equal('foo')
+        client.should.have.property('appId').equal('bar')
+    })
+
+    it('should initialize when passing an object containing orgId and appId, taking precedence over config', function() {
+        var client = new UsergridClient({
+            orgId: 'foo',
+            appId: 'bar',
+            baseUrl: 'https://sdk-example-test.apigee.net/appservices'
+        })
+        client.should.be.an.instanceof(UsergridClient).with.property('orgId').equal('foo')
+        client.should.have.property('appId').equal('bar')
+        client.should.have.property('baseUrl').equal('https://sdk-example-test.apigee.net/appservices')
+    })
+})
\ No newline at end of file
diff --git a/tests/lib/client.rest.test.js b/tests/lib/client.rest.test.js
new file mode 100644
index 0000000..017073e
--- /dev/null
+++ b/tests/lib/client.rest.test.js
@@ -0,0 +1,481 @@
+'use strict'
+
+var config = require('../../helpers').config,
+    chance = new require('chance').Chance(),
+    UsergridClient = require('../../lib/client'),
+    UsergridEntity = require('../../lib/entity'),
+    UsergridQuery = require('../../lib/query'),
+    _ = require('lodash')
+
+var _uuid,
+    _slow = 500,
+    _timeout = 4000
+
+describe('GET()', function() {
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    var response, client
+    before(function(done) {
+        client = new UsergridClient(config)
+        client.GET(config.test.collection, function(err, usergridResponse) {
+            response = usergridResponse
+            done()
+        })
+    })
+
+    it('should not fail when a callback function is not passed', function() {
+        // note: this test will NOT fail gracefully inside the Mocha event chain
+        client.GET(config.test.collection)
+    })
+
+    it('response.ok should be true', function() {
+        response.ok.should.be.true()
+    })
+
+    it('response.entities should be an array', function() {
+        response.entities.should.be.an.Array()
+    })
+
+    it('response.first should exist and have a valid uuid', function() {
+        response.first.should.be.an.instanceof(UsergridEntity).with.property('uuid').which.is.a.uuid()
+    })
+
+    it('response.entity should exist and have a valid uuid', function() {
+        response.entity.should.be.an.instanceof(UsergridEntity).with.property('uuid').which.is.a.uuid()
+    })
+
+    it('response.last should exist and have a valid uuid', function() {
+        response.last.should.be.an.instanceof(UsergridEntity).with.property('uuid').which.is.a.uuid()
+    })
+
+    it('each entity should match the search criteria when passing a UsergridQuery object', function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+
+        var query = new UsergridQuery(config.test.collection).eq('color', 'black')
+
+        client.GET(query, function(err, usergridResponse) {
+            usergridResponse.entities.forEach(function(entity) {
+                entity.should.be.an.Object().with.property('color').equal('black')
+            })
+            done()
+        })
+    })
+
+    it('a single entity should be retrieved when specifying a uuid', function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+
+        client.GET(config.test.collection, response.entity.uuid, function(err, usergridResponse) {
+            usergridResponse.should.have.property('entity').which.is.an.instanceof(UsergridEntity)
+            usergridResponse.body.entities.should.be.an.Array().with.a.lengthOf(1)
+            done()
+        })
+    })
+})
+
+describe('POST()', function() {
+
+    this.slow(_slow)
+    this.timeout(3000)
+
+    var response, client
+    before(function(done) {
+        client = new UsergridClient()
+        client.POST(config.test.collection, {
+            author: 'Sir Arthur Conan Doyle'
+        }, function(err, usergridResponse) {
+            response = usergridResponse
+            _uuid = usergridResponse.entity.uuid
+            done()
+        })
+    })
+
+    it('should not fail when a callback function is not passed', function() {
+        // note: this test will NOT fail gracefully inside the Mocha event chain
+        client.POST(config.test.collection, {})
+    })
+
+    it('response.ok should be true', function() {
+        response.ok.should.be.true()
+    })
+
+    it('response.entities should be an array', function() {
+        response.entities.should.be.an.Array().with.a.lengthOf(1)
+    })
+
+    it('response.entity should exist and have a valid uuid', function() {
+        response.entity.should.be.an.instanceof(UsergridEntity).with.property('uuid').which.is.a.uuid()
+    })
+
+    it('response.entity.author should equal "Sir Arthur Conan Doyle"', function() {
+        response.entity.should.have.property('author').equal('Sir Arthur Conan Doyle')
+    })
+
+    it('should support creating an entity by passing a UsergridEntity object', function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+
+        var entity = new UsergridEntity({
+            type: config.test.collection,
+            restaurant: "Dino's Deep Dish",
+            cuisine: "pizza"
+        })
+
+        client.POST(entity, function(err, usergridResponse) {
+            usergridResponse.entity.should.be.an.Object().with.property('restaurant').equal(entity.restaurant)
+            done()
+        })
+    })
+
+    it('should support creating an entity by passing a UsergridEntity object with a unique name', function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+
+        var entity = new UsergridEntity({
+            type: config.test.collection,
+            name: chance.word()
+        })
+        client.POST(entity, function(err, usergridResponse) {
+            usergridResponse.entity.should.be.an.Object().with.property('name').equal(entity.name)
+            usergridResponse.entity.remove(client)
+            done()
+        })
+    })
+
+    it('should support creating an entity by passing type and a body object', function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+
+        client.POST(config.test.collection, {
+            restaurant: "Dino's Deep Dish",
+            cuisine: "pizza"
+        }, function(err, usergridResponse) {
+            usergridResponse.entity.should.be.an.Object().with.property('restaurant').equal("Dino's Deep Dish")
+            done()
+        })
+    })
+
+    it('should support creating an entity by passing a body object that includes type', function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+
+        client.POST({
+            type: config.test.collection,
+            restaurant: "Dino's Deep Dish",
+            cuisine: "pizza"
+        }, function(err, usergridResponse) {
+            usergridResponse.entity.should.be.an.Object().with.property('restaurant').equal("Dino's Deep Dish")
+            done()
+        })
+    })
+
+    it('should support creating an entity by passing an array of UsergridEntity objects', function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+
+        var entities = [
+            new UsergridEntity({
+                type: config.test.collection,
+                restaurant: "Dino's Deep Dish",
+                cuisine: "pizza"
+            }), new UsergridEntity({
+                type: config.test.collection,
+                restaurant: "Chipotle",
+                cuisine: "mexican"
+            })
+        ]
+
+        client.POST(entities, function(err, usergridResponse) {
+            usergridResponse.entities.forEach(function(entity) {
+                entity.should.be.an.Object().with.property('restaurant').equal(entity.restaurant)
+            })
+            done()
+        })
+    })
+
+    it('should support creating an entity by passing a preformatted POST builder object', function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+
+        var options = {
+            type: config.test.collection,
+            body: {
+                restaurant: "Chipotle",
+                cuisine: "mexican"
+            }
+        }
+
+        client.POST(options, function(err, usergridResponse) {
+            usergridResponse.entity.should.be.an.Object().with.property('restaurant').equal(usergridResponse.entity.restaurant)
+            done()
+        })
+    })
+})
+
+describe('PUT()', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    var response, client
+    before(function(done) {
+        client = new UsergridClient()
+        client.PUT(config.test.collection, _uuid, {
+            narrator: 'Peter Doyle'
+        }, function(err, usergridResponse) {
+            response = usergridResponse
+            done()
+        })
+    })
+
+    it('should not fail when a callback function is not passed', function() {
+        // note: this test will NOT fail gracefully inside the Mocha event chain
+        client.PUT(config.test.collection, _uuid, {})
+    })
+
+    it('response.ok should be true', function() {
+        response.ok.should.be.true()
+    })
+
+    it('response.entities should be an array with a single entity', function() {
+        response.entities.should.be.an.Array().with.a.lengthOf(1)
+    })
+
+    it('response.entity should exist and its uuid should the uuid from the previous POST request', function() {
+        response.entity.should.be.an.Object().with.property('uuid').equal(_uuid)
+    })
+
+    it('response.entity.narrator should be updated to "Peter Doyle"', function() {
+        response.entity.should.have.property('narrator').equal('Peter Doyle')
+    })
+
+    it('should create a new entity when no uuid or name is passed', function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+
+        var newEntity = new UsergridEntity({
+            type: config.test.collection,
+            author: 'Frank Mills'
+        })
+
+        client.PUT(newEntity, function(err, usergridResponse) {
+            usergridResponse.entity.should.be.an.Object()
+            usergridResponse.entity.should.be.an.instanceof(UsergridEntity).with.property('uuid').which.is.a.uuid()
+            usergridResponse.entity.should.have.property('author').equal('Frank Mills')
+            usergridResponse.entity.created.should.equal(usergridResponse.entity.modified)
+            done()
+        })
+    })
+
+    it('should support updating the entity by passing a UsergridEntity object', function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+
+        var updateEntity = _.assign(response.entity, {
+            publisher: {
+                name: "George Newns",
+                date: "14 October 1892",
+                country: "United Kingdom"
+            }
+        })
+
+        client.PUT(updateEntity, function(err, usergridResponse) {
+            usergridResponse.entity.should.be.an.Object().with.property('publisher').deepEqual(updateEntity.publisher)
+            done()
+        })
+    })
+
+    it('should support updating an entity by passing type and a body object', function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+        client.PUT(config.test.collection, {
+            uuid: response.entity.uuid,
+            updateByPassingTypeAndBody: true
+        }, function(err, usergridResponse) {
+            usergridResponse.entity.should.be.an.Object().with.property('updateByPassingTypeAndBody').equal(true)
+            done()
+        })
+    })
+
+    it('should support updating an entity by passing a body object that includes type', function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+
+        client.PUT(config.test.collection, {
+            type: config.test.collection,
+            uuid: response.entity.uuid,
+            updateByPassingBodyIncludingType: true
+        }, function(err, usergridResponse) {
+            usergridResponse.entity.should.be.an.Object().with.property('updateByPassingBodyIncludingType').equal(true)
+            done()
+        })
+    })
+
+    it('should support updating a set of entities by passing an UsergridQuery object', function(done) {
+
+        this.slow(_slow + 1000)
+        this.timeout(_timeout + 10000)
+
+        var query = new UsergridQuery(config.test.collection).eq('cuisine', 'pizza').limit(2)
+        var body = {
+            testUuid: _.uuid()
+        }
+
+        client.PUT(query, body, function(err, usergridResponse) {
+            usergridResponse.entities.forEach(function(entity) {
+                entity.should.be.an.Object().with.property('testUuid').equal(body.testUuid)
+            })
+            done()
+        })
+    })
+
+    it('should support updating an entity by passing a preformatted PUT builder object', function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+
+        var options = {
+            uuidOrName: response.entity.uuid,
+            type: config.test.collection,
+            body: {
+                relatedUuid: _.uuid()
+            }
+        }
+
+        client.PUT(options, function(err, usergridResponse) {
+            usergridResponse.entity.should.be.an.Object().with.property('relatedUuid').equal(options.body.relatedUuid)
+            done()
+        })
+    })
+})
+
+describe('DELETE()', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    var response, client
+    before(function(done) {
+        client = new UsergridClient()
+        client.DELETE(config.test.collection, _uuid, function() {
+            client.GET(config.test.collection, _uuid, function(err, usergridResponse) {
+                response = usergridResponse
+                done()
+            })
+        })
+    })
+
+    it('should not fail when a callback function is not passed', function() {
+        // note: this test will NOT fail gracefully inside the Mocha event chain
+        client.DELETE(config.test.collection, _uuid)
+    })
+
+    it('should return a 404 not found', function() {
+        response.statusCode.should.equal(404)
+    })
+
+    it('response.error.name should equal "entity_not_found"', function() {
+        response.error.name.should.equal((config.target === '1.0') ? 'service_resource_not_found' : 'entity_not_found')
+    })
+
+    it('should support deleting an entity by passing a UsergridEntity object', function(done) {
+
+        this.slow(_slow + 1000)
+        this.timeout(_timeout + 1000)
+
+        var entity = new UsergridEntity({
+            type: config.test.collection,
+            command: "CTRL+ALT+DEL"
+        })
+
+        client.POST(entity, function(err, usergridResponse) {
+            client.DELETE(usergridResponse.entity, function() {
+                client.GET(config.test.collection, usergridResponse.entity.uuid, function(err, delResponse) {
+                    delResponse.ok.should.be.false()
+                    delResponse.error.name.should.equal((config.target === '1.0') ? 'service_resource_not_found' : 'entity_not_found')
+                    done()
+                })
+            })
+        })
+    })
+
+    it('should support deleting an entity by passing type and uuid', function(done) {
+
+        this.slow(_slow + 1000)
+        this.timeout(_timeout + 1000)
+
+        var body = {
+            command: "CTRL+ALT+DEL"
+        }
+
+        client.POST(config.test.collection, body, function(err, usergridResponse) {
+            client.DELETE(config.test.collection, usergridResponse.entity.uuid, function() {
+                client.GET(config.test.collection, usergridResponse.entity.uuid, function(err, delResponse) {
+                    delResponse.error.name.should.equal((config.target === '1.0') ? 'service_resource_not_found' : 'entity_not_found')
+                    done()
+                })
+            })
+        })
+    })
+
+    it('should support deleting multiple entities by passing a UsergridQuery object', function(done) {
+
+        this.slow(_slow + 1000)
+        this.timeout(_timeout + 6000)
+
+        var entity = new UsergridEntity({
+            type: config.test.collection,
+            command: "CMD+TAB"
+        })
+
+        var query = new UsergridQuery(config.test.collection).eq('command', 'CMD+TAB')
+
+        client.POST([entity, entity, entity, entity], function() {
+            client.DELETE(query, function() {
+                client.GET(query, function(err, usergridResponse) {
+                    usergridResponse.entities.should.be.an.Array().with.lengthOf(0)
+                    done()
+                })
+            })
+        })
+    })
+
+    it('should support deleting an entity by passing a preformatted DELETE builder object', function(done) {
+
+        this.slow(_slow + 1000)
+        this.timeout(_timeout + 1000)
+
+        var options = {
+            type: config.test.collection,
+            body: {
+                restaurant: "IHOP",
+                cuisine: "breakfast"
+            }
+        }
+
+        client.POST(options, function(err, usergridResponse) {
+            client.DELETE(_.assign(options, {
+                uuid: usergridResponse.entity.uuid
+            }), function() {
+                client.GET(config.test.collection, usergridResponse.entity.uuid, function(err, delResponse) {
+                    delResponse.error.name.should.equal((config.target === '1.0') ? 'service_resource_not_found' : 'entity_not_found')
+                    done()
+                })
+            })
+        })
+    })
+})
\ No newline at end of file
diff --git a/tests/lib/entity.test.js b/tests/lib/entity.test.js
new file mode 100644
index 0000000..8152f23
--- /dev/null
+++ b/tests/lib/entity.test.js
@@ -0,0 +1,696 @@
+'use strict'
+
+var should = require('should'),
+    urljoin = require('url-join'),
+    config = require('../../helpers').config,
+    UsergridClient = require('../../lib/client'),
+    UsergridEntity = require('../../lib/entity'),
+    UsergridQuery = require('../../lib/query'),
+    UsergridAuth = require('../../lib/auth'),
+    UsergridAsset = require('../../lib/asset'),
+    fs = require('fs')
+
+var _slow = 1500,
+    _timeout = 4000,
+    filename = 'old_man',
+    file = __dirname + '/image.jpg',
+    expectedContentLength = 109055,
+    assetEntity = new UsergridEntity({
+        type: config.test.collection,
+        info: "assetTestEntity"
+    })
+
+describe('putProperty()', function() {
+    it('should set the value for a given key if the key does not exist', function() {
+        var entity = new UsergridEntity('cat', 'Cosmo')
+        entity.putProperty('foo', ['bar'])
+        entity.should.have.property('foo').deepEqual(['bar'])
+    })
+
+    it('should overwrite the value for a given key if the key exists', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: 'baz'
+        })
+        entity.putProperty('foo', 'bar')
+        entity.should.have.property('foo').deepEqual('bar')
+    })
+
+    it('should not be able to set the name key (name is immutable)', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: 'baz'
+        })
+        should(function() {
+            entity.putProperty('name', 'Bazinga')
+        }).throw()
+    })
+})
+
+describe('putProperties()', function() {
+    it('should set properties for a given object, overwriting properties that exist and creating those that don\'t', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: 'bar'
+        })
+        entity.putProperties({
+            foo: 'baz',
+            qux: 'quux',
+            barray: [1, 2, 3, 4]
+        })
+        entity.should.containEql({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: 'baz',
+            qux: 'quux',
+            barray: [1, 2, 3, 4]
+        })
+    })
+
+    it('should not be able to set properties for immutable keys', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: 'baz'
+        })
+        entity.putProperties({
+            name: 'Bazinga',
+            uuid: 'BadUuid',
+            bar: 'qux'
+        })
+        entity.should.containEql({
+            type: 'cat',
+            name: 'Cosmo',
+            bar: 'qux',
+            foo: 'baz'
+        })
+    })
+})
+
+describe('removeProperty()', function() {
+    it('should remove a given property if it exists', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: 'baz'
+        })
+        entity.removeProperty('foo')
+        entity.should.not.have.property('foo')
+    })
+
+    it('should fail gracefully when removing an undefined property', function() {
+        var entity = new UsergridEntity('cat', 'Cosmo')
+        entity.removeProperty('foo')
+        entity.should.not.have.property('foo')
+    })
+})
+
+describe('removeProperties()', function() {
+    it('should remove an array of properties for a given object, failing gracefully for undefined properties', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: 'bar',
+            baz: 'qux'
+        })
+        entity.removeProperties(['foo', 'baz'])
+        entity.should.containEql({
+            type: 'cat',
+            name: 'Cosmo'
+        })
+    })
+})
+
+describe('insert()', function() {
+    it('should insert a single value into an existing array at the specified index', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: [1, 2, 3, 5, 6]
+        })
+        entity.insert('foo', 4, 3)
+        entity.should.have.property('foo').deepEqual([1, 2, 3, 4, 5, 6])
+    })
+
+    it('should merge an array of values into an existing array at the specified index', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: [1, 2, 3, 7, 8]
+        })
+        entity.insert('foo', [4, 5, 6], 3)
+        entity.should.have.property('foo').deepEqual([1, 2, 3, 4, 5, 6, 7, 8])
+    })
+
+    it('should convert an existing value into an array when inserting a second value', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: 'bar'
+        })
+        entity.insert('foo', 'baz', 1)
+        entity.should.have.property('foo').deepEqual(['bar', 'baz'])
+    })
+
+    it('should create a new array when a property does not exist', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo'
+        })
+        entity.insert('foo', 'bar', 1000)
+        entity.should.have.property('foo').deepEqual(['bar'])
+    })
+
+    it('should gracefully handle indexes out of range', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: ['bar']
+        })
+        entity.insert('foo', 'baz', 1000)
+        entity.should.have.property('foo').deepEqual(['bar', 'baz'])
+        entity.insert('foo', 'qux', -1000)
+        entity.should.have.property('foo').deepEqual(['qux', 'bar', 'baz'])
+    })
+})
+
+describe('append()', function() {
+    it('should append a value to the end of an existing array', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: [1, 2, 3]
+        })
+        entity.append('foo', 4)
+        entity.should.have.property('foo').deepEqual([1, 2, 3, 4])
+    })
+
+    it('should create a new array if a property does not exist', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo'
+        })
+        entity.append('foo', 'bar')
+        entity.should.have.property('foo').deepEqual(['bar'])
+    })
+})
+
+describe('prepend()', function() {
+    it('should prepend a value to the beginning of an existing array', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: [1, 2, 3]
+        })
+        entity.prepend('foo', 0)
+        entity.should.have.property('foo').deepEqual([0, 1, 2, 3])
+    })
+
+    it('should create a new array if a property does not exist', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo'
+        })
+        entity.prepend('foo', 'bar')
+        entity.should.have.property('foo').deepEqual(['bar'])
+    })
+})
+
+describe('pop()', function() {
+    it('should remove the last value of an existing array', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: [1, 2, 3]
+        })
+        entity.pop('foo')
+        entity.should.have.property('foo').deepEqual([1, 2])
+    })
+
+    it('value should remain unchanged if it is not an array', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: 'bar'
+        })
+        entity.pop('foo')
+        entity.should.have.property('foo').deepEqual('bar')
+    })
+
+    it('should gracefully handle empty arrays', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: []
+        })
+        entity.pop('foo')
+        entity.should.have.property('foo').deepEqual([])
+    })
+})
+
+describe('shift()', function() {
+    it('should remove the first value of an existing array', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: [1, 2, 3]
+        })
+        entity.shift('foo')
+        entity.should.have.property('foo').deepEqual([2, 3])
+    })
+
+    it('value should remain unchanged if it is not an array', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: 'bar'
+        })
+        entity.shift('foo')
+        entity.should.have.property('foo').deepEqual('bar')
+    })
+
+    it('should gracefully handle empty arrays', function() {
+        var entity = new UsergridEntity({
+            type: 'cat',
+            name: 'Cosmo',
+            foo: []
+        })
+        entity.shift('foo')
+        entity.should.have.property('foo').deepEqual([])
+    })
+})
+
+describe('reload()', function() {
+
+    this.slow(_slow + 1000)
+    this.timeout(_timeout + 4000)
+
+    it('should refresh an entity with the latest server copy of itself', function(done) {
+        var client = new UsergridClient(config),
+            now = Date.now()
+        client.GET(config.test.collection, function(err, getResponse) {
+            var entity = new UsergridEntity(getResponse.first)
+            var modified = entity.modified
+            getResponse.first.putProperty('reloadTest', now)
+            client.PUT(getResponse.first, function() {
+                entity.reload(client, function() {
+                    client.isSharedInstance.should.be.false()
+                    entity.reloadTest.should.equal(now)
+                    entity.modified.should.not.equal(modified)
+                    done()
+                })
+            })
+        })
+    })
+})
+
+describe('save()', function() {
+
+    this.slow(_slow + 1000)
+    this.timeout(_timeout + 4000)
+
+    it('should save an updated entity to the server', function(done) {
+        var client = new UsergridClient(config),
+            now = Date.now()
+        client.GET(config.test.collection, function(err, getResponse) {
+            var entity = new UsergridEntity(getResponse.first)
+            entity.putProperty('saveTest', now)
+            entity.save(client, function() {
+                client.isSharedInstance.should.be.false()
+                entity.saveTest.should.equal(now)
+                done()
+            })
+        })
+    })
+})
+
+describe('remove()', function() {
+
+    this.slow(_slow + 1000)
+    this.timeout(_timeout + 4000)
+
+    it('should remove an entity from the server', function(done) {
+        var client = new UsergridClient(config)
+        client.POST(config.test.collection, {
+            removeTest: 'test'
+        }, function(err, postResponse) {
+            var entity = new UsergridEntity(postResponse.first)
+            entity.remove(client, function(err, deleteResponse) {
+                client.isSharedInstance.should.be.false()
+                deleteResponse.ok.should.be.true()
+                    // best practice is to desroy the 'entity' instance here, because it no longer exists on the server
+                entity = null
+                done()
+            })
+        })
+    })
+})
+
+describe('attachAsset()', function() {
+
+    var asset = new UsergridAsset(filename, file),
+        entity = new UsergridEntity({
+            type: config.test.collection,
+            info: "attachAssetTest"
+        })
+    before(function(done) {
+        fs.readFile(file, function(err, data) {
+            asset.data = data
+            done()
+        })
+    })
+
+    it('should attach a UsergridAsset to the entity', function(done) {
+        entity.attachAsset(asset)
+        entity.should.have.property('asset').equal(asset)
+        done()
+    })
+})
+
+describe('uploadAsset()', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    var asset = new UsergridAsset(filename, file)
+    before(function(done) {
+        fs.readFile(file, function(err, data) {
+            asset.data = data
+            done()
+        })
+    })
+
+    it('should upload an image asset to the remote entity', function(done) {
+        var client = new UsergridClient(config)
+        assetEntity.attachAsset(asset)
+        assetEntity.uploadAsset(client, function(err, usergridResponse, entity) {
+            assetEntity = entity
+            entity.should.have.property('file-metadata')
+            entity['file-metadata'].should.have.property('content-length').equal(expectedContentLength)
+            entity['file-metadata'].should.have.property('content-type').equal('image/jpeg')
+            done()
+        })
+    })
+})
+
+describe('downloadAsset()', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    it('should download a an image from the remote entity', function(done) {
+        var client = new UsergridClient(config)
+        assetEntity.downloadAsset(client, 'image/jpeg', function(err, usergridResponse, entityWithAsset) {
+            entityWithAsset.should.have.property('asset').which.is.an.instanceof(UsergridAsset)
+            entityWithAsset.asset.should.have.property('contentType').equal(assetEntity['file-metadata']['content-type'])
+            entityWithAsset.asset.should.have.property('contentLength').equal(assetEntity['file-metadata']['content-length'])
+            // clean up the now un-needed asset entity
+            entityWithAsset.remove(client)
+            done()
+        })
+    })
+})
+
+describe('connect()', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout + 4000)
+
+    var response,
+        entity1,
+        entity2,
+        client = new UsergridClient(),
+        query = new UsergridQuery(config.test.collection).eq('name', 'testEntityConnectOne').or.eq('name', 'testEntityConnectTwo').asc('name')
+
+    before(function(done) {
+        // Create the entities we're going to use for connections
+        client.POST(config.test.collection, [{
+            "name": "testEntityConnectOne"
+        }, {
+            "name": "testEntityConnectTwo"
+        }], function() {
+            client.GET(query, function(err, usergridResponse) {
+                response = usergridResponse
+                entity1 = response.first
+                entity2 = response.last
+                done()
+            })
+        })
+    })
+
+    it('should connect entities by passing a target UsergridEntity object as a parameter', function(done) {
+        var relationship = "foos"
+
+        entity1.connect(client, relationship, entity2, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, relationship, function(err, usergridResponse) {
+                usergridResponse.first.metadata.connecting[relationship].should.equal(urljoin(
+                    "",
+                    config.test.collection,
+                    entity1.uuid,
+                    relationship,
+                    entity2.uuid,
+                    "connecting",
+                    relationship
+                ))
+                done()
+            })
+        })
+    })
+
+    it('should connect entities by passing target uuid as a parameter', function(done) {
+        var relationship = "bars"
+
+        entity1.connect(client, relationship, entity2.uuid, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, relationship, function(err, usergridResponse) {
+                usergridResponse.first.metadata.connecting[relationship].should.equal(urljoin(
+                    "",
+                    config.test.collection,
+                    entity1.uuid,
+                    relationship,
+                    entity2.uuid,
+                    "connecting",
+                    relationship
+                ))
+                done()
+            })
+        })
+    })
+
+    it('should connect entities by passing target type and name as parameters', function(done) {
+        var relationship = "bazzes"
+
+        entity1.connect(client, relationship, entity2.type, entity2.name, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, relationship, function(err, usergridResponse) {
+                usergridResponse.first.metadata.connecting[relationship].should.equal(urljoin(
+                    "",
+                    config.test.collection,
+                    entity1.uuid,
+                    relationship,
+                    entity2.uuid,
+                    "connecting",
+                    relationship
+                ))
+                done()
+            })
+        })
+    })
+
+    it('should fail to connect entities when specifying target name without type', function() {
+        should(function() {
+            entity1.connect("fails", 'badName', function() {})
+        }).throw()
+    })
+})
+
+describe('getConnections()', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout + 4000)
+
+    var response,
+        client = new UsergridClient(),
+        query = new UsergridQuery(config.test.collection).eq('name', 'testEntityConnectOne').or.eq('name', 'testEntityConnectTwo').asc('name')
+
+    before(function(done) {
+        client.GET(query, function(err, usergridResponse) {
+            response = usergridResponse
+            done()
+        })
+    })
+
+    it('should get an entity\'s outbound connections', function(done) {
+        var entity1 = response.first
+        var entity2 = response.last
+
+        var relationship = "foos"
+
+        entity1.getConnections(client, UsergridClient.Connections.DIRECTION_OUT, relationship, function(err, usergridResponse) {
+            usergridResponse.first.metadata.connecting[relationship].should.equal(urljoin(
+                "",
+                config.test.collection,
+                entity1.uuid,
+                relationship,
+                entity2.uuid,
+                "connecting",
+                relationship
+            ))
+            done()
+        })
+    })
+
+    it('should get an entity\'s inbound connections', function(done) {
+        var entity1 = response.first
+        var entity2 = response.last
+
+        var relationship = "foos"
+
+        entity2.getConnections(client, UsergridClient.Connections.DIRECTION_IN, relationship, function(err, usergridResponse) {
+            usergridResponse.first.metadata.connections[relationship].should.equal(urljoin(
+                "",
+                config.test.collection,
+                entity2.uuid,
+                "connecting",
+                entity1.uuid,
+                relationship
+            ))
+            done()
+        })
+    })
+})
+
+describe('disconnect()', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout + 4000)
+
+    var response,
+        client = new UsergridClient(),
+        query = new UsergridQuery(config.test.collection).eq('name', 'testEntityConnectOne').or.eq('name', 'testEntityConnectTwo').asc('name')
+
+    before(function(done) {
+        client.GET(query, function(err, usergridResponse) {
+            response = usergridResponse
+            done()
+        })
+    })
+
+    it('should disconnect entities by passing a target UsergridEntity object as a parameter', function(done) {
+        var entity1 = response.first
+        var entity2 = response.last
+
+        var relationship = "foos"
+
+        entity1.disconnect(client, relationship, entity2, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, relationship, function(err, usergridResponse) {
+                usergridResponse.entities.should.be.an.Array().with.lengthOf(0)
+                done()
+            })
+        })
+    })
+
+    it('should disconnect entities by passing target uuid as a parameter', function(done) {
+        var entity1 = response.first
+        var entity2 = response.last
+
+        var relationship = "bars"
+
+        entity1.disconnect(client, relationship, entity2.uuid, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, relationship, function(err, usergridResponse) {
+                usergridResponse.entities.should.be.an.Array().with.lengthOf(0)
+                done()
+            })
+        })
+    })
+
+    it('should disconnect entities by passing target type and name as parameters', function(done) {
+        var entity1 = response.first
+        var entity2 = response.last
+
+        var relationship = "bazzes"
+
+        entity1.disconnect(client, relationship, entity2.type, entity2.name, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            client.getConnections(UsergridClient.Connections.DIRECTION_OUT, entity1, relationship, function(err, usergridResponse) {
+                usergridResponse.entities.should.be.an.Array().with.lengthOf(0)
+                done()
+            })
+        })
+    })
+
+    it('should fail to disconnect entities when specifying target name without type', function() {
+        var entity1 = response.first
+        var entity2 = response.last
+
+        should(function() {
+            entity1.disconnect("fails", entity2.name, function() {})
+        }).throw()
+    })
+})
+
+describe('usingAuth()', function() {
+
+    this.slow(_slow + 500)
+    this.timeout(_timeout)
+
+    var client = new UsergridClient(),
+        authFromToken = new UsergridAuth('BADTOKEN'),
+        _entity
+
+    it('should fail to reload an entity when using a bad ad-hoc token', function(done) {
+        client.GET(config.test.collection, function(err, getResponse) {
+            _entity = new UsergridEntity(getResponse.first)
+            _entity.usingAuth(authFromToken).reload(client, function(error, usergridResponse) {
+                usergridResponse.request.headers.should.not.have.property('authentication')
+                usergridResponse.ok.should.be.false()
+                error.name.should.equal('auth_bad_access_token')
+                done()
+            })
+        })
+    })
+
+    it('client.tempAuth should be destroyed after making a request with ad-hoc authentication', function(done) {
+        should(client.tempAuth).be.undefined()
+        done()
+    })
+
+    it('entity.tempAuth should be destroyed after making a request with ad-hoc authentication', function(done) {
+        should(_entity.tempAuth).be.undefined()
+        done()
+    })
+
+    it('should send an unauthenticated request when UsergridAuth.NO_AUTH is passed to .usingAuth()', function(done) {
+        // hack this using 'me' in case test apps have sandbox permissions
+        var entity = new UsergridEntity({
+            uuid: 'me',
+            type: 'users'
+        })
+        entity.usingAuth(UsergridAuth.NO_AUTH).reload(client, function(error, usergridResponse) {
+            usergridResponse.request.headers.should.not.have.property('authentication')
+            usergridResponse.ok.should.be.false()
+            error.name.should.equal('service_resource_not_found')
+            done()
+        })
+    })
+
+    it('should send an unauthenticated request when no arguments are passed to .usingAuth()', function(done) {
+        // hack this using 'me' in case test apps have sandbox permissions
+        var entity = new UsergridEntity({
+            uuid: 'me',
+            type: 'users'
+        })
+        entity.usingAuth().reload(client, function(error, usergridResponse) {
+            usergridResponse.request.headers.should.not.have.property('authentication')
+            usergridResponse.ok.should.be.false()
+            error.name.should.equal('service_resource_not_found')
+            done()
+        })
+    })
+})
\ No newline at end of file
diff --git a/tests/lib/image.jpg b/tests/lib/image.jpg
new file mode 100644
index 0000000..32f4fa3
--- /dev/null
+++ b/tests/lib/image.jpg
Binary files differ
diff --git a/tests/lib/image_test.jpg b/tests/lib/image_test.jpg
new file mode 100644
index 0000000..32f4fa3
--- /dev/null
+++ b/tests/lib/image_test.jpg
Binary files differ
diff --git a/tests/lib/query.test.js b/tests/lib/query.test.js
new file mode 100644
index 0000000..e92c34d
--- /dev/null
+++ b/tests/lib/query.test.js
@@ -0,0 +1,106 @@
+'use strict'
+
+var UsergridQuery = require('../../lib/query')
+
+describe('_type', function() {
+    it('_type should equal "cats" when passing "type" as a parameter to UsergridQuery', function() {
+        var query = new UsergridQuery('cats')
+        query.should.have.property('_type').equal('cats')
+    })
+
+    it('_type should equal "cats" when calling .type() builder method', function() {
+        var query = new UsergridQuery().type('cats')
+        query.should.have.property('_type').equal('cats')
+    })
+
+    it('_type should equal "cats" when calling .collection() builder method', function() {
+        var query = new UsergridQuery().collection('cats')
+        query.should.have.property('_type').equal('cats')
+    })
+})
+
+describe('_limit', function() {
+    it('_limit should equal 2 when setting .limit(2)', function() {
+        var query = new UsergridQuery('cats').limit(2)
+        query.should.have.property('_limit').equal(2)
+    })
+
+    it('_limit should equal 10 when setting .limit(10)', function() {
+        var query = new UsergridQuery('cats').limit(10)
+        query.should.have.property('_limit').equal(10)
+    })
+})
+
+describe('_ql', function() {
+    it('should equal \'select *\' if query or sort are empty or underfined', function() {
+        var query = new UsergridQuery('cats')
+        query.should.have.property('_ql').equal('select *')
+    })
+
+    it('should support complex builder pattern syntax (chained constructor methods)', function() {
+        var query = new UsergridQuery('cats')
+            .gt('weight', 2.4)
+            .contains('color', 'bl*')
+            .not
+            .eq('color', 'blue')
+            .or
+            .eq('color', 'orange')
+        query.should.have.property('_ql').equal("select * where weight > 2.4 and color contains 'bl*' and not color = 'blue' or color = 'orange'")
+    })
+
+    it('and operator should be implied when joining multiple conditions', function() {
+        var query1 = new UsergridQuery('cats')
+            .gt('weight', 2.4)
+            .contains('color', 'bl*')
+        query1.should.have.property('_ql').equal("select * where weight > 2.4 and color contains 'bl*'")
+        var query2 = new UsergridQuery('cats')
+            .gt('weight', 2.4)
+            .and
+            .contains('color', 'bl*')
+        query2.should.have.property('_ql').equal("select * where weight > 2.4 and color contains 'bl*'")
+    })
+
+    it('not operator should precede conditional statement', function() {
+        var query = new UsergridQuery('cats')
+            .not
+            .eq('color', 'white')
+        query.should.have.property('_ql').equal("select * where not color = 'white'")
+    })
+
+    it('fromString should set _ql directly, bypassing builder pattern methods', function() {
+        var q = "where color = 'black' or color = 'orange'"
+        var query = new UsergridQuery('cats')
+            .fromString(q)
+        query.should.have.property('_ql').equal(q)
+    })
+
+    it('string values should be contained in single quotes', function() {
+        var query = new UsergridQuery('cats')
+            .eq('color', 'black')
+        query.should.have.property('_ql').equal("select * where color = 'black'")
+    })
+
+    it('boolean values should not be contained in single quotes', function() {
+        var query = new UsergridQuery('cats')
+            .eq('longHair', true)
+        query.should.have.property('_ql').equal("select * where longHair = true")
+    })
+
+    it('float values should not be contained in single quotes', function() {
+        var query = new UsergridQuery('cats')
+            .lt('weight', 18)
+        query.should.have.property('_ql').equal("select * where weight < 18")
+    })
+
+    it('integer values should not be contained in single quotes', function() {
+        var query = new UsergridQuery('cats')
+            .gte('weight', 2)
+        query.should.have.property('_ql').equal("select * where weight >= 2")
+    })
+
+    it('uuid values should not be contained in single quotes', function() {
+        var query = new UsergridQuery('cats')
+            .eq('uuid', 'a61e29ba-944f-11e5-8690-fbb14f62c803')
+        query.should.have.property('_ql').equal("select * where uuid = a61e29ba-944f-11e5-8690-fbb14f62c803")
+    })
+})
\ No newline at end of file
diff --git a/tests/lib/response.test.js b/tests/lib/response.test.js
new file mode 100644
index 0000000..685d132
--- /dev/null
+++ b/tests/lib/response.test.js
@@ -0,0 +1,188 @@
+'use strict'
+
+var should = require('should'),
+    config = require('../../helpers').config,
+    UsergridClient = require('../../lib/client'),
+    UsergridEntity = require('../../lib/entity'),
+    UsergridUser = require('../../lib/user'),
+    UsergridQuery = require('../../lib/query'),
+    UsergridResponseError = require('../../lib/responseError'),
+    _ = require('lodash')
+
+var client = new UsergridClient()
+
+var _response,
+    _slow = 500,
+    _timeout = 4000
+
+before(function(done) {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    client.GET(config.test.collection, function(err, usergridResponse) {
+        _response = usergridResponse
+        done()
+    })
+})
+
+describe('headers', function() {
+    it('should be an object', function() {
+        _response.headers.should.be.an.Object().with.property('content-type')
+    })
+})
+
+describe('statusCode', function() {
+    it('should be a number', function() {
+        _response.statusCode.should.be.a.Number()
+    })
+})
+
+describe('ok', function() {
+    it('should be a bool', function() {
+        _response.ok.should.be.a.Boolean()
+    })
+})
+
+describe('metadata', function() {
+    it('should be a read-only object', function() {
+        _response.metadata.should.be.an.Object().with.any.properties(['action', 'application', 'path', 'uri', 'timestamp', 'duration'])
+        Object.isFrozen(_response.metadata).should.be.true()
+        should(function() {
+            _response.metadata.uri = 'TEST'
+        }).throw()
+    })
+})
+
+describe('error', function() {
+    it('should be a UsergridResponseError object', function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+
+        client.GET(config.test.collection, 'BADNAMEORUUID', function(err, usergridResponse) {
+            usergridResponse.error.should.be.an.instanceof(UsergridResponseError)
+            done()
+        })
+    })
+})
+
+describe('users', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    it('response.users should be an array of UsergridUser objects', function(done) {
+        client.setAppAuth(config.clientId, config.clientSecret, config.tokenTtl)
+        client.authenticateApp(function(err) {
+            should(err).be.undefined()
+            client.GET('users', function(err, usergridResponse) {
+                usergridResponse.ok.should.be.true()
+                usergridResponse.users.should.be.an.Array()
+                usergridResponse.users.forEach(function(user) {
+                    user.should.be.an.instanceof(UsergridUser)
+                })
+                done()
+            })
+        })
+    })
+})
+
+describe('user', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    var user
+
+    it('response.user should be a UsergridUser object and have a valid uuid matching the first object in response.users', function(done) {
+        client.setAppAuth(config.clientId, config.clientSecret, config.tokenTtl)
+        client.authenticateApp(function(err) {
+            should(err).be.undefined()
+            client.GET('users', function(err, usergridResponse) {
+                user = usergridResponse.user
+                user.should.be.an.instanceof(UsergridUser).with.property('uuid').equal(_.first(usergridResponse.entities).uuid)
+                done()
+            })
+        })
+    })
+
+    it('response.user should be a subclass of UsergridEntity', function(done) {
+        user.isUser.should.be.true()
+        user.should.be.an.instanceof(UsergridEntity)
+        done()
+    })
+})
+
+describe('entities', function() {
+    it('should be an array of UsergridEntity objects', function() {
+        _response.entities.should.be.an.Array()
+        _response.entities.forEach(function(entity) {
+            entity.should.be.an.instanceof(UsergridEntity)
+        })
+    })
+})
+
+describe('first, entity', function() {
+    it('response.first should be a UsergridEntity object and have a valid uuid matching the first object in response.entities', function() {
+        _response.first.should.be.an.instanceof(UsergridEntity).with.property('uuid').equal(_.first(_response.entities).uuid)
+    })
+
+    it('response.entity should be a reference to response.first', function() {
+        _response.should.have.property('entity').deepEqual(_response.first)
+    })
+})
+
+describe('last', function() {
+    it('last should be a UsergridEntity object and have a valid uuid matching the last object in response.entities', function() {
+        _response.last.should.be.an.instanceof(UsergridEntity).with.property('uuid').equal(_.last(_response.entities).uuid)
+    })
+})
+
+describe('hasNextPage', function() {
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    it('should be true when more entities exist', function(done) {
+        client.GET(config.test.collection, function(err, usergridResponse) {
+            usergridResponse.hasNextPage.should.be.true()
+            done()
+        })
+    })
+
+    it('should be false when no more entities exist', function(done) {
+        client.GET('users', function(err, usergridResponse) {
+            usergridResponse.metadata.count.should.be.lessThan(10)
+            usergridResponse.hasNextPage.should.not.be.true()
+            done()
+        })
+    })
+})
+
+describe('loadNextPage()', function() {
+    this.slow(_slow + 800)
+    this.timeout(_timeout + 2000)
+
+    var firstResponse
+
+    before(function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+
+        var query = new UsergridQuery(config.test.collection).limit(10)
+
+        client.GET(query, function(err, usergridResponse) {
+            firstResponse = usergridResponse
+            done()
+        })
+    })
+
+    it('should load a new page of entities by passing an instance of UsergridClient', function(done) {
+        firstResponse.loadNextPage(client, function(err, usergridResponse) {
+            usergridResponse.first.uuid.should.not.equal(firstResponse.first.uuid)
+            usergridResponse.entities.length.should.equal(10)
+            done()
+        })
+    })
+})
\ No newline at end of file
diff --git a/tests/lib/responseError.test.js b/tests/lib/responseError.test.js
new file mode 100644
index 0000000..f746279
--- /dev/null
+++ b/tests/lib/responseError.test.js
@@ -0,0 +1,47 @@
+'use strict'
+
+var should = require('should'),
+    config = require('../../helpers').config,
+    UsergridClient = require('../../lib/client'),
+    UsergridResponseError = require('../../lib/responseError')
+
+var client = new UsergridClient()
+
+var _response,
+    _slow = 500,
+    _timeout = 4000
+
+describe('name, description, exception', function() {
+
+    before(function(done) {
+
+        this.slow(_slow)
+        this.timeout(_timeout)
+
+        client.GET(config.test.collection, 'BADNAMEORUUID', function(err, usergridResponse) {
+            _response = usergridResponse
+            done()
+        })
+    })
+
+    it('response.statusCode should be greater than or equal to 400', function() {
+        _response.ok.should.be.false()
+    })
+
+    it('response.error should be a UsergridResponseError object with name, description, and exception keys', function() {
+        _response.ok.should.be.false()
+        _response.error.should.be.an.instanceof(UsergridResponseError).with.properties(['name', 'description', 'exception'])
+    })
+})
+
+describe('undefined check', function() {
+    it('response.error should be undefined on a successful response', function(done) {
+        this.slow(_slow)
+        this.timeout(_timeout)
+        client.GET(config.test.collection, function(err, usergridResponse) {
+            usergridResponse.ok.should.be.true()
+            should(usergridResponse.error).be.undefined()
+            done()
+        })
+    })
+})
\ No newline at end of file
diff --git a/tests/lib/user.test.js b/tests/lib/user.test.js
new file mode 100644
index 0000000..ef89a20
--- /dev/null
+++ b/tests/lib/user.test.js
@@ -0,0 +1,273 @@
+'use strict'
+
+var should = require('should'),
+    util = require('util'),
+    chance = new require('chance').Chance(),
+    config = require('../../helpers').config,
+    UsergridClient = require('../../lib/client'),
+    UsergridUser = require('../../lib/user'),
+    UsergridQuery = require('../../lib/query')
+
+var _slow = 1500,
+    _timeout = 4000,
+    _username1 = chance.word(),
+    _user1 = new UsergridUser({
+        username: _username1,
+        password: config.test.password
+    }),
+    client = new UsergridClient(config)
+
+before(function(done) {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    var query = new UsergridQuery('users').not.eq('username', config.test.username).limit(20)
+    // clean up old user entities as the UsergridResponse tests rely on this collection containing less than 10 entities
+    client.DELETE(query, function() {
+        _user1.create(client, function() {
+            done()
+        })
+    })
+})
+
+describe('create()', function() {
+
+    it(util.format("should create a new user with the username '%s'", _username1), function() {
+        _user1.username.should.equal(_username1)
+    })
+
+    it('should have a valid uuid', function() {
+        _user1.should.have.property('uuid').which.is.a.uuid()
+    })
+
+    it('should have a created date', function() {
+        _user1.should.have.property('created')
+    })
+
+    it('should be activated (i.e. has a valid password)', function() {
+        _user1.should.have.property('activated').true()
+    })
+
+    it('should not have a password property', function() {
+        _user1.should.not.have.property('password')
+    })
+
+    it('should fail gracefully when a username already exists', function(done) {
+        var user = new UsergridUser({
+            username: _username1,
+            password: config.test.password
+        })
+        user.create(client, function(err, usergridResponse) {
+            err.should.not.be.null()
+            err.should.containDeep({
+                name: 'duplicate_unique_property_exists'
+            })
+            usergridResponse.ok.should.be.false()
+            done()
+        })
+    })
+
+    it('should create a new user on the server', function(done) {
+        var username = chance.word()
+        var user = new UsergridUser({
+            username: username,
+            password: config.test.password
+        })
+        user.create(client, function(err, usergridResponse, user) {
+            client.isSharedInstance.should.be.false()
+            user.username.should.equal(username)
+            user.should.have.property('uuid').which.is.a.uuid()
+            user.should.have.property('created')
+            user.should.have.property('activated').true()
+            user.should.not.have.property('password')
+                // cleanup
+            user.remove(client, function() {
+                done()
+            })
+        })
+    })
+})
+
+describe('login()', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    it(util.format("it should log in the user '%s' and receive a token", _username1), function(done) {
+        _user1.password = config.test.password
+        _user1.login(client, function(err, response, token) {
+            _user1.auth.should.have.property('token').equal(token)
+            _user1.should.not.have.property('password')
+            _user1.auth.should.not.have.property('password')
+            done()
+        })
+    })
+})
+
+describe('logout()', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    it(util.format("it should log out '%s' and destroy the saved UsergridUserAuth instance", _username1), function(done) {
+        _user1.logout(client, function(err, response) {
+            response.ok.should.be.true()
+            response.body.action.should.equal("revoked user token")
+            _user1.auth.isValid.should.be.false()
+            done()
+        })
+    })
+
+    it("it should return an error when attempting to log out a user that does not have a valid token", function(done) {
+        _user1.logout(client, function(err) {
+            err.should.containDeep({
+                name: 'no_valid_token'
+            })
+            done()
+        })
+    })
+})
+
+describe('logoutAllSessions()', function() {
+    it(util.format("it should log out all tokens for the user '%s' destroy the saved UsergridUserAuth instance", _username1), function(done) {
+        _user1.password = config.test.password
+        _user1.login(client, function() {
+            _user1.logoutAllSessions(client, function(err, response) {
+                response.ok.should.be.true()
+                response.body.action.should.equal("revoked user tokens")
+                _user1.auth.isValid.should.be.false()
+                done()
+            })
+        })
+    })
+})
+
+describe('resetPassword()', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    it(util.format("it should reset the password for '%s' by passing parameters", _username1), function(done) {
+        _user1.resetPassword(client, config.test.password, '2cool4u', function(err, response) {
+            response.ok.should.be.true()
+            response.body.action.should.equal("set user password")
+            done()
+        })
+    })
+
+    it(util.format("it should reset the password for '%s' by passing an object", _username1), function(done) {
+        _user1.resetPassword(client, {
+            oldPassword: '2cool4u',
+            newPassword: config.test.password
+        }, function(err, response) {
+            response.ok.should.be.true()
+            response.body.action.should.equal("set user password")
+            done()
+        })
+    })
+
+    it(util.format("it should not reset the password for '%s' when passing a bad old password", _username1), function(done) {
+        _user1.resetPassword(client, {
+            oldPassword: 'BADOLDPASSWORD',
+            newPassword: config.test.password
+        }, function(err, response) {
+            response.ok.should.be.false()
+            err.name.should.equal('auth_invalid_username_or_password')
+            _user1.remove(client, function() {
+                done()
+            })
+        })
+    })
+
+    it("it should return an error when attempting to reset a password with missing arguments", function() {
+        should(function() {
+            _user1.resetPassword(client, 'NEWPASSWORD', function() {})
+        }).throw()
+    })
+})
+
+describe('CheckAvailable()', function() {
+
+    this.slow(_slow)
+    this.timeout(_timeout)
+
+    var nonExistentEmail = util.format('%s@%s.com', chance.word(), chance.word())
+    var nonExistentUsername = chance.word()
+
+    it(util.format("it should return true for username '%s'", config.test.username), function(done) {
+        UsergridUser.CheckAvailable(client, {
+            username: config.test.username
+        }, function(err, response, exists) {
+            exists.should.be.true()
+            done()
+        })
+    })
+
+    it(util.format("it should return true for email '%s'", config.test.email), function(done) {
+        UsergridUser.CheckAvailable(client, {
+            email: config.test.email
+        }, function(err, response, exists) {
+            exists.should.be.true()
+            done()
+        })
+    })
+
+    it(util.format("it should return true for email '%s' and non-existent username '%s'", config.test.email, nonExistentUsername), function(done) {
+        UsergridUser.CheckAvailable(client, {
+            email: config.test.email,
+            username: nonExistentUsername
+        }, function(err, response, exists) {
+            exists.should.be.true()
+            done()
+        })
+    })
+
+    it(util.format("it should return true for non-existent email '%s' and username '%s'", nonExistentEmail, config.test.username), function(done) {
+        UsergridUser.CheckAvailable(client, {
+            email: nonExistentEmail,
+            username: config.test.username
+        }, function(err, response, exists) {
+            exists.should.be.true()
+            done()
+        })
+    })
+
+    it(util.format("it should return true for email '%s' and username '%s'", config.test.email, config.test.username), function(done) {
+        UsergridUser.CheckAvailable(client, {
+            email: config.test.email,
+            username: config.test.useranme
+        }, function(err, response, exists) {
+            exists.should.be.true()
+            done()
+        })
+    })
+
+    it(util.format("it should return false for non-existent email '%s'", nonExistentEmail), function(done) {
+        UsergridUser.CheckAvailable(client, {
+            email: nonExistentEmail
+        }, function(err, response, exists) {
+            exists.should.be.false()
+            done()
+        })
+    })
+
+    it(util.format("it should return false for non-existent username '%s'", nonExistentUsername), function(done) {
+        UsergridUser.CheckAvailable(client, {
+            username: nonExistentUsername
+        }, function(err, response, exists) {
+            exists.should.be.false()
+            done()
+        })
+    })
+
+    it(util.format("it should return false for non-existent email '%s' and non-existent username '%s'", nonExistentEmail, nonExistentUsername), function(done) {
+        UsergridUser.CheckAvailable(client, {
+            email: nonExistentEmail,
+            username: nonExistentUsername
+        }, function(err, response, exists) {
+            exists.should.be.false()
+            done()
+        })
+    })
+})
\ No newline at end of file
diff --git a/tests/lib/usergrid.init.test.js b/tests/lib/usergrid.init.test.js
new file mode 100644
index 0000000..b8a9fd9
--- /dev/null
+++ b/tests/lib/usergrid.init.test.js
@@ -0,0 +1,21 @@
+'use strict'
+
+var config = require('../../helpers').config,
+    Usergrid = require('../../usergrid'),
+    UsergridClient = require('../../lib/client'),
+    util = require('util'),
+    _ = require('lodash') 
+
+describe('init() / initSharedInstance()', function() {
+    it('should be an instance of UsergridClient', function(done) {
+        Usergrid.init()
+        Usergrid.initSharedInstance()
+        Usergrid.should.be.an.instanceof(UsergridClient)
+        done()
+    })
+    
+    it(util.format('should be testing against a Usergrid v%s instance', config.target), function(done) {
+        util.format('%s', config.target).should.equal((_.last(process.argv)).startsWith('--target=') ? _.last(process.argv).replace(/--target=/, '') : '2.1')
+        done()
+    })
+})
\ No newline at end of file
diff --git a/tests/lib/usergrid.singleton.test.js b/tests/lib/usergrid.singleton.test.js
new file mode 100644
index 0000000..9f73bab
--- /dev/null
+++ b/tests/lib/usergrid.singleton.test.js
@@ -0,0 +1,9 @@
+'use strict'
+
+var Usergrid = require('../../usergrid')
+
+it('should be initialized when defined in another module', function(done) {
+    Usergrid.should.have.property('isInitialized').which.is.true()
+    Usergrid.should.have.property('isSharedInstance').which.is.true()
+    done()
+})
\ No newline at end of file
diff --git a/tests/lib/usergrid.teardown.test.js b/tests/lib/usergrid.teardown.test.js
new file mode 100644
index 0000000..d50ecd4
--- /dev/null
+++ b/tests/lib/usergrid.teardown.test.js
@@ -0,0 +1,14 @@
+'use strict'
+
+var Usergrid = require('../../usergrid'),
+    UsergridClient = require('../../lib/client')
+
+it('should be destroyed', function(done) {
+    // destroy shared instance to prevent other tests from defaulting to use the shared instance
+    var UsergridRef = require.resolve('../../usergrid')
+    delete require.cache[UsergridRef]
+    Usergrid = require('../../usergrid')
+    Usergrid.should.have.property('isInitialized').which.is.false()
+    Usergrid.should.not.be.an.instanceof(UsergridClient)
+    done()
+})
\ No newline at end of file
diff --git a/tests/main.test.js b/tests/main.test.js
new file mode 100644
index 0000000..22bbedd
--- /dev/null
+++ b/tests/main.test.js
@@ -0,0 +1,75 @@
+'use strict'
+
+// module config
+var should = require('should'),
+    _ = require('lodash')
+
+_.mixin(require('lodash-uuid'))
+
+should.Assertion.add('uuid', function() {
+    this.params = {
+        operator: 'to be a valid uuid'
+    }
+    this.assert(_.isUuid(this.obj))
+})
+
+should.Assertion.add('buffer', function() {
+    this.params = {
+        operator: 'to be buffer data'
+    }
+    this.assert(Buffer.isBuffer(this.obj))
+})
+
+// end module config
+
+describe('Usergrid initialization', function() {
+    return require('./lib/usergrid.init.test')
+})
+
+describe('Usergrid singleton', function() {
+    return require('./lib/usergrid.singleton.test')
+})
+
+describe('Usergrid teardown', function() {
+    return require('./lib/usergrid.teardown.test')
+})
+
+describe('UsergridClient initialization', function() {
+    return require('./lib/client.init.test')
+})
+
+describe('UsergridClient REST operations', function() {
+    return require('./lib/client.rest.test')
+})
+
+describe('UsergridClient connections', function() {
+    return require('./lib/client.connections.test')
+})
+
+describe('UsergridClient authentication', function() {
+    return require('./lib/client.auth.test')
+})
+
+describe('UsergridQuery', function() {
+    return require('./lib/query.test')
+})
+
+describe('UsergridResponse', function() {
+    return require('./lib/response.test')
+})
+
+describe('UsergridResponseError', function() {
+    return require('./lib/responseError.test')
+})
+
+describe('UsergridEntity', function() {
+    return require('./lib/entity.test')
+})
+
+describe('UsergridUser', function() {
+    return require('./lib/user.test')
+})
+
+describe('UsergridAsset', function() {
+    return require('./lib/asset.test')
+})
\ No newline at end of file
diff --git a/usergrid.js b/usergrid.js
new file mode 100644
index 0000000..cc6ba27
--- /dev/null
+++ b/usergrid.js
@@ -0,0 +1,37 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict'
+
+var _ = require('lodash')
+
+var Usergrid = {
+    isInitialized: false,
+    isSharedInstance: true,
+    initSharedInstance: function(options) {
+        var self = this
+        if (self.isInitialized) {
+            console.log('Usergrid shared instance is already initialized')
+            return self
+        }
+        var UsergridClient = require('./lib/client')
+        Object.setPrototypeOf(self, new UsergridClient(options))
+        _.assign(self, new UsergridClient(options))
+        self.isInitialized = true
+        self.isSharedInstance = true
+    }
+}
+
+Usergrid.init = Usergrid.initSharedInstance
+module.exports = Usergrid
\ No newline at end of file