Repository: auth0/node-auth0
Pull request #54 (2015-12-04 19:08:43)

Implement 100% of the non-interactive endpoints of the Authentication API
This PR includes the implementation, tests and docs for the following endpoints:

+ OAuth authentication
   - Sign in (username/password)
   - Social sign in (social access token)

+ Database & Active Directory authentication
   - Sign in
   - Sign up
   - Change password

+ Passwordless authentication
   - Sign in
   - Send email
   - Send SMS

+ User data endpoints
   - Get user info
   - Impersonate user

+ Token endpoints
   - Get token info
   - Get delegation token

This PR also adds:
   - Table of Contents to the README.
   - Improved inline documentation for all classes.
   - Bindings for authenticator methods in the AuthenticationClient class (same method names than Auth0.js).


Patch:
diff --git a/.editorconfig b/.editorconfig
index d4058cd5d..150a05b06 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -14,4 +14,4 @@ trim_trailing_whitespace = true
 insert_final_newline = true
 
 [*.md]
-trim_trailing_whitespace = false
+trim_trailing_whitespace = true
diff --git a/README.md b/README.md
index a09178921..156fb1dcb 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,103 @@
-
-
-# node-auth0 [![Coverage Status](https://coveralls.io/repos/sophilabs/node-auth0/badge.svg?branch=v2&service=github)](https://coveralls.io/github/sophilabs/node-auth0?branch=v2) [![Build Status](https://travis-ci.org/sophilabs/node-auth0.svg?branch=v2)](https://travis-ci.org/sophilabs/node-auth0)
+[![Coverage Status](https://coveralls.io/repos/sophilabs/node-auth0/badge.svg?branch=v2&service=github)](https://coveralls.io/github/sophilabs/node-auth0?branch=v2) [![Build Status](https://travis-ci.org/sophilabs/node-auth0.svg?branch=v2)](https://travis-ci.org/sophilabs/node-auth0)
 
 Node.js client library for the [Auth0](https://auth0.com) platform.
 
-## Installation
-
-	npm install auth0@2.0.0-beta.3
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+##Table of Contents
+
+- [Installation](#installation)
+- [Management API Client](#management-api-client)
+  - [Usage](#usage)
+  - [Clients](#clients)
+    - [Get all clients](#get-all-clients)
+    - [Create a client](#create-a-client)
+    - [Get a client](#get-a-client)
+    - [Delete a client](#delete-a-client)
+    - [Update a client](#update-a-client)
+  - [Connections](#connections)
+    - [Get all connections](#get-all-connections)
+    - [Create a connection](#create-a-connection)
+    - [Get a connection](#get-a-connection)
+    - [Delete a connection](#delete-a-connection)
+    - [Update a connection](#update-a-connection)
+  - [Device Credentials](#device-credentials)
+    - [List device credentials](#list-device-credentials)
+    - [Create device public key](#create-device-public-key)
+    - [Delete a device credential](#delete-a-device-credential)
+  - [Rules](#rules)
+    - [Get all rules](#get-all-rules)
+    - [Create a rule](#create-a-rule)
+    - [Get a rule](#get-a-rule)
+    - [Delete a rule](#delete-a-rule)
+    - [Update a rule](#update-a-rule)
+  - [Users](#users)
+    - [List or search users](#list-or-search-users)
+    - [Create a user](#create-a-user)
+    - [Get a user](#get-a-user)
+    - [Delete all users](#delete-all-users)
+    - [Delete a user](#delete-a-user)
+    - [Update a user](#update-a-user)
+    - [Update user metadata](#update-user-metadata)
+    - [Update app metadata](#update-app-metadata)
+    - [Link user accounts](#link-user-accounts)
+    - [Unlink user accounts](#unlink-user-accounts)
+    - [Delete user multifactor provider](#delete-user-multifactor-provider)
+  - [Blacklisted Tokens](#blacklisted-tokens)
+    - [Get all blacklisted tokens](#get-all-blacklisted-tokens)
+    - [Blacklist a token](#blacklist-a-token)
+  - [Email Provider](#email-provider)
+    - [Get the email provider](#get-the-email-provider)
+    - [Configure the email provider](#configure-the-email-provider)
+    - [Delete the email provider](#delete-the-email-provider)
+    - [Update the email provider](#update-the-email-provider)
+- [Authentication API Client](#authentication-api-client)
+  - [Usage](#usage-1)
+  - [Database & Active Directory](#database-&-active-directory)
+    - [Sign in](#sign-in)
+    - [Sign up](#sign-up)
+    - [Change password](#change-password)
+  - [Passwordless](#passwordless)
+    - [Send email](#send-email)
+    - [Send SMS](#send-sms)
+    - [Login](#login)
+  - [Users](#users-1)
+    - [User info](#user-info)
+    - [Impersonation](#impersonation)
+  - [Tokens](#tokens)
+    - [Token info](#token-info)
+    - [Delegation Token](#delegation-token)
+- [General](#general)
+  - [Promises and Callbacks](#promises-and-callbacks)
+  - [Examples](#examples)
+  - [Documentation](#documentation)
+  - [What is Auth0?](#what-is-auth0)
+  - [Create a free Auth0 Account](#create-a-free-auth0-account)
+  - [Issue Reporting](#issue-reporting)
+  - [Author](#author)
+  - [License](#license)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+# Installation
+
+	npm install auth0@2.0.0-alpha.5
+
+# Management API Client
+The Auth0 Management API is meant to be used by back-end servers or trusted parties performing administrative tasks. Generally speaking, anything that can be done through the Auth0 dashboard (and more) can also be done through this API.
 
 ## Usage
 
 Initialize your client class with an API v2 token (you can generate one [here](https://auth0.com/docs/apiv2)) and a domain.
 
 ~~~js
-var token = '{YOUR_API_V2_TOKEN}';
-var auth0 = require('auth0')({
-  token: token,
-  domain: 'YOUR_ACCOUNT.auth0.com'
+var ManagementClient = require('auth0').ManagementClient;
+var auth0 = new ManagementClient({
+  token: '{YOUR_API_V2_TOKEN}',
+  domain: '{YOUR_ACCOUNT}.auth0.com'
 });
 ~~~
 
-
-## Promises and Callbacks
-Be aware that all methods can be used with Promises or callbacks. However, when a callback is provided, no Promise will be returned.
-
-~~~js
-// Using callbacks.
-auth0.getUsers(function (err, users) {
-  if (err) {
-    // Handle error.
-  }
-  console.log(users);
-});
-
-
-// Using promises.
-auth0
-  .getUsers()
-  .then(function (users) {
-    console.log(users);
-  })
-  .catch(function (err) {
-    // Handle error.
-  });
-~~~
-
 ## Clients
 [Clients](https://auth0.com/docs/api/v2#!/Clients) represent applications. You can learn more about this in the [Applications](https://auth0.com/docs/applications) section of the documentation.
 
@@ -588,21 +641,46 @@ auth0.users.update(params, data, function (err, user) {
 });
 ~~~
 
-### Update user and app metadata
+### Update user metadata
 
 ~~~js
 var params = { id: USER_ID };
-var data = {
-  app_metadata: {
-    foo: 'bar'
-  },
-  user_metadata: {
-    address: '123th Node.js Street'
+var metadata = {
+  address: '123th Node.js Street'
+};
+
+// Using auth0 instance.
+auth0.updateUserMetadata(params, metadata, function (err, user) {
+  if (err) {
+    // Handle error.
   }
+
+  // Updated user.
+  console.log(user);
+});
+
+// Using the users manager directly.
+auth0.users.updateUserMetadata(params, metadata, function (err, user) {
+  if (err) {
+    // Handle error.
+  }
+
+  // Updated user.
+  console.log(user);
+});
+~~~
+
+
+### Update app metadata
+
+~~~js
+var params = { id: USER_ID };
+var metadata = {
+  foo: 'bar'
 };
 
 // Using auth0 instance.
-auth0.updateUser(params, data, function (err, user) {
+auth0.updateAppMetadata(params, metadata, function (err, user) {
   if (err) {
     // Handle error.
   }
@@ -612,7 +690,7 @@ auth0.updateUser(params, data, function (err, user) {
 });
 
 // Using the users manager directly.
-auth0.users.update(params, data, function (err, user) {
+auth0.users.updateAppMetadata(params, metadata, function (err, user) {
   if (err) {
     // Handle error.
   }
@@ -622,6 +700,84 @@ auth0.users.update(params, data, function (err, user) {
 });
 ~~~
 
+### Link user accounts
+
+~~~js
+var params = { id: USER_ID };
+var data = {
+	user_id: 'OTHER_USER_ID',
+	connection_id: 'CONNECTION_ID'
+};
+
+// Using auth0 instance.
+auth0.linkUsers(params, data, function (err, user) {
+  if (err) {
+    // Handle error.
+  }
+
+  // Users linked.
+});
+
+// Using the users manager directly.
+auth0.users.link(params, data, function (err, user) {
+  if (err) {
+    // Handle error.
+  }
+
+  // Users linked.
+});
+~~~
+
+### Unlink user accounts
+
+~~~js
+var params = { id: USER_ID, provider: 'auht0', user_id: OTHER_USER_ID };
+
+// Using auth0 instance.
+auth0.unlinkUsers(params, function (err, user) {
+  if (err) {
+    // Handle error.
+  }
+
+  // Users accounts unlinked.
+});
+
+// Using the users manager directly.
+auth0.users.unlink(params, function (err, user) {
+  if (err) {
+    // Handle error.
+  }
+
+  // Users accounts unlinked.
+});
+~~~
+
+
+### Delete user multifactor provider
+
+~~~js
+var params = { id: USER_ID, provider: MULTIFACTOR_PROVIDER };
+
+// Using auth0 instance.
+auth0.deleteUserMultifcator(params, function (err, user) {
+  if (err) {
+    // Handle error.
+  }
+
+  // Users accounts unlinked.
+});
+
+// Using the users manager directly.
+auth0.users.deleteMultifactorProvider(params, function (err, user) {
+  if (err) {
+    // Handle error.
+  }
+
+  // Users accounts unlinked.
+});
+~~~
+
+
 
 ## Blacklisted Tokens
 Managing [Blacklisted tokens](https://auth0.com/docs/api/v2#!/Blacklists) with the SDK.
@@ -756,9 +912,422 @@ auth0.emailProvider.update(function (err, provider) {
 });
 ~~~
 
-## Authentication
 
-This library can be used to access Auth0's [API v2](https://auth0.com/docs/apiv2). To authenticate users use the [passport strategy](https://github.com/auth0/passport-auth0).
+## Jobs
+
+### Get a job
+~~~js
+var params = {
+  id: '{JOB_ID}'
+};
+
+auth0.jobs.get(params, function (err, job) {
+  if (err) {
+    // Handle error.
+  }
+  
+  // Retrieved job.
+  console.log(job);
+});
+~~~
+
+### Import users
+~~~js
+var params = {
+  connection_id: '{CONNECTION_ID}',
+  users: '{PATH_TO_USERS_FILE}'
+};
+
+auth0.jobs.get(params, function (err) {
+  if (err) {
+    // Handle error.
+  }
+});
+~~~
+
+### Request email verification
+~~~js
+var params = {
+	user_id: '{USER_ID}'
+};
+
+auth0.jobs.verifyEmail(function (err) { 
+  if (err) {
+    // Handle error.
+  }
+});
+~~~
+
+## Stats
+
+### Get daily stats
+~~~js
+var params = {
+  from: '{YYYYMMDD}',  // First day included in the stats.
+  to: '{YYYYMMDD}'  // Last day included in the stats.
+};
+
+auth0.stats.getDaily(params, function (err, stats) {
+  if (err) {
+    // Handle error.
+  }
+  
+  console.log(stats);
+});
+~~~
+
+### Get active users count
+~~~js
+auth0.stats.getActiveUsersCount(function (err, usersCount) {
+  if (err) {
+    // Handle error.
+  }
+  
+  console.log(usersCount);
+});
+~~~
+
+## Tenant
+
+### Get tenant settings
+~~~js
+auth0.tenant.getSettings(function (err, settings) {
+  if (err) {
+    // Handle error.
+  } 
+  
+  console.log(settings);
+});
+~~~
+
+### Update tenant settings
+~~~js
+auth0.tenant.updateSettings(data, function (err) {
+  if (err) {
+    // Handle error.
+  }
+});
+~~~
+
+## Tickets
+
+### Create change password ticket
+More info in the [API Docs](https://auth0.com/docs/api/v2#!/Tickets/post_password_change).
+
+~~~js
+var params = {
+  result_url: '{REDIRECT_URL}',  // Redirect after using the ticket.
+  user_id: '{USER_ID}',  // Optional.
+  email: '{USER_EMAIL}',  // Optional.
+  new_password: '{PASSWORD}'
+}; 
+~~~
+
+### Create email verification ticket.
+~~~js
+var data = {
+  user_id: '{USER_ID}',
+  result_url: '{REDIRECT_URL}' // Optional redirect after the ticket is used.
+};
+
+auth0.tickets.verifyEmail(data, function (err) {
+  if (err) {
+    // Handle error.
+  }	
+});
+~~~
+
+# Authentication API Client
+
+This client can be used to access Auth0's [Authentication API](https://auth0.com/docs/auth-api).
+
+## Usage
+
+The **AuthenticationClient** constructor takes an *optional* client ID, if specified it will be used as default value for all endpoints that accept a client ID.
+
+~~~js
+var AuthenticationClient = require('auth0'). AuthenticationClient;
+var auth0 = new AuthenticationClient({
+  domain: '{YOUR_ACCOUNT}.auth0.com',
+  clientId: '{OPTIONAL_CLIENT_ID}'
+});
+~~~
+
+## Social
+
+### Sign in
+Given the social provider's access_token and the connection specified, it will do the authentication on the provider and return a JSON with the access_token and id_token. Currently, this endpoint only works for Facebook, Google, Twitter and Weibo. More information in the [API Docs](https://auth0.com/docs/auth-api#!#post--oauth-access_token).
+
+~~~js
+var data = {
+  client_id: '{CLIENT_ID}',  // Optional field.
+  access_token: '{USER_SOCIAL_ACCESS_TOKEN}',
+  connection: 'facebook',
+  scope: 'openid'  // Optional field.
+};
+
+auth0.oauth.socialSignIn(data, function (err, userData) {
+  if (err) {
+    // Handle error.
+  }
+
+  console.log(userData);
+});
+~~~
+
+## Database & Active Directory
+
+### Sign in
+Given the user credentials and the connection specified, it will do the authentication on the provider and return a JSON with the `access_token` and `id_token`. Find more information about the structure of the data object in the [API docs](https://auth0.com/docs/auth-api#!#post--oauth-ro).
+
+~~~js
+var data = {
+  username: '{USERNAME}',
+  password: '{PASSWORD}',
+  connection: 'Username-Password-Authentication' // Optional field.
+};
+
+auth0.database.signIn(data, function (err, userData) {
+  if (err) {
+    // Handle error.
+  }
+
+  console.log(userData);
+});
+~~~
+
+### Sign up
+Given the user credentials, the connection specified and (optionally) the client ID, it will create a new user. Find more information in the [API Docs](https://auth0.com/docs/auth-api#!#post--dbconnections-signup).
+
+~~~js
+var data = {
+  email: '{EMAIL}',
+  password: '{PASSWORD}',
+  connection: 'Username-Password-Authentication' // Optional field.
+};
+
+auth0.database.signUp(data, function (err, userData) {
+  if (err) {
+    // Handle error.
+  }
+
+  console.log(userData);
+});
+~~~
+
+### Change password
+Given the user email, the connection specified and the new password to use, Auth0 will send a forgot password email. Once the user clicks on the confirm password change link, the new password specified in this POST will be set to this user. Find more information in the [API Docs](https://auth0.com/docs/auth-api#!#post--dbconnections-change_password).
+
+~~~js
+var data = {
+  email: '{EMAIL}',
+  password: '{PASSWORD}',
+  connection: 'Username-Password-Authentication'
+};
+
+// Using the database authenticator.
+auth0.database.changePassword(data, function (err, message) {
+  if (err) {
+    // Handle error.
+  }
+
+  console.log(message);
+});
+
+// Using the Authentication client.
+auth0.changePassword(data, function (err, message) {
+  if (err) {
+    // Handle error.
+  }
+
+  console.log(message);
+});
+~~~
+
+## Passwordless
+
+### Send email
+Given the user `email` address, it will send an email with:
+
+- A link (default, `send:"link"`). You can then authenticate with this user opening the link and he will be automatically logged in to the application. Optionally, you can append/override parameters to the link (like `scope`, `redirect_uri`, `protocol`, `response_type`, etc.) using `authParams` object.
+- A verification code (`send:"code"`). You can then authenticate with this user using the `/oauth/ro` endpoint specifying `email` as `username` and `code` as `password`.
+
+Find more information in the [API Docs](https://auth0.com/docs/auth-api#!#post--with_email).
+
+~~~js
+
+var data = {
+  email: '{EMAIL}',
+  send: 'link',
+  authParams: {} // Optional auth params.
+};
+
+// Using the passwordless authenticator.
+auth0.passwordless.sendEmail(data, function (err) {
+  if (err) {
+    // Handle error.
+  }
+});
+
+// Using the authentication client (the 'send' property will be ignored here).
+auth0.requestMagicLink(data, function (err) { ... };
+auth0.requestEmailCode(data, function (err) { ... };
+~~~
+
+### Send SMS
+Given the user `phone_number`, it will send a SMS message with a verification code. You can then authenticate with this user using the `/oauth/ro` endpoint specifying `phone_number` as `username` and `code` as `password`:
+
+~~~js
+
+var data = {
+  phone_number: '{PHONE}'
+};
+
+// Using the passwordless authenticator.
+auth0.passwordless.sendSMS(data, function (err) {
+  if (err) {
+    // Handle error.
+  }
+});
+
+// Using the authentication client.
+auth0.requestSMSCode(data, function (err) { ... });
+~~~
+
+### Login
+Given the user credentials (`phone_number` and `code`), it will do the authentication on the provider and return a JSON with the `access_token` and `id_token`.
+
+~~~js
+var data = {
+  username: '{PHONE_NUMBER}',
+  password: '{VERIFICATION_CODE}'
+};
+
+// Using the passwordless authenticator.
+auth0.passwordless.signIn(data, function (err) {
+  if (err) {
+    // Handle error.
+  }
+});
+
+// Using the authentication client.
+auth0.verifySMSCode(data, function (err) { ... });
+~~~
+
+The user data object has the following structure.
+
+~~~js
+{
+  id_token: String,
+  access_token: String,
+  token_type: String
+}
+~~~
+
+## Users
+
+### User info
+Get the user information based on the Auth0 access token (obtained during login). Find more information in the [API Docs](https://auth0.com/docs/auth-api#!#get--userinfo).
+
+~~~js
+// Using the users manager.
+auth0.users.getInfo(accessToken, function (err, userInfo) {
+  if (err) {
+    // Handle error.
+  }
+
+  console.log(userInfo);
+});
+
+// Using the authentication client.
+auth0.getProfile(data, function (err, userInfo) { ... });
+~~~
+
+### Impersonation
+Gets a link that can be used once to log in as a specific user. Useful for troubleshooting. Find more information in the [API Docs](https://auth0.com/docs/auth-api#!#post--users--user_id--impersonate).
+
+~~~js
+var settings = {
+  impersonator_id: '{IMPERSONATOR_ID}',
+  protocol: 'oauth2',
+  additionalParameters: {}  // Optional aditional params.
+};
+
+auth0.users.impersonate(userId, settings, function (err, link) {
+  if (err) {
+    // Handle error.
+  }
+
+  console.log(link);
+});
+~~~
+
+## Tokens
+
+### Token info
+Validates a JSON Web Token (signature and expiration) and returns the user information associated with the user id (sub property) of the token. Find more information in the [API Docs](https://auth0.com/docs/auth-api#!#post--tokeninfo).
+
+~~~js
+auth0.tokens.getInfo(token, function (err, tokenInfo) {
+  if (err) {
+    // Handle error.
+  }
+
+  console.log(tokenInfo);
+});
+~~~
+
+### Delegation Token
+Given an existing token, this endpoint will generate a new token signed with the target client secret. This is used to flow the identity of the user from the application to an API or across different APIs that are protected with different secrets. Find more information in the [API Docs](https://auth0.com/docs/auth-api#!#post--delegation).
+
+~~~js
+var data = {
+  id_token: '{ID_TOKEN}',
+  api_type: 'app',
+  target: '{TARGET}',
+  grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer'
+};
+
+// Using the tokens manager.
+auth0.tokens.getDelegationToken(data, function (err, token) {
+  if (err) {
+    // Handle error.
+  }
+
+  console.log(token);
+});
+
+// Using the authentication client.
+auth0.getDelegationToken(data, function (err, token) { ... });
+~~~
+
+# General
+
+## Promises and Callbacks
+
+Be aware that all methods can be used with Promises or callbacks. However, when a callback is provided no Promise will be returned.
+
+~~~js
+// Using callbacks.
+auth0.getUsers(function (err, users) {
+  if (err) {
+    // Handle error.
+  }
+  console.log(users);
+});
+
+
+// Using promises.
+auth0
+  .getUsers()
+  .then(function (users) {
+    console.log(users);
+  })
+  .catch(function (err) {
+    // Handle error.
+  });
+~~~
+
 
 ## Examples
 
diff --git a/package.json b/package.json
index e2cf03e65..dfe37bc02 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "auth0",
-  "version": "2.0.0-beta.3",
+  "version": "2.0.0-alpha.5",
   "description": "node.js SDK for Auth0 API v2",
   "main": "index.js",
   "scripts": {
@@ -24,7 +24,7 @@
   "homepage": "https://github.com/auth0/node-auth0",
   "dependencies": {
     "bluebird": "^2.10.2",
-    "rest-facade": "^1.2.3",
+    "rest-facade": "^1.2.7",
     "url-join": "0.0.1",
     "superagent": "^1.1.0"
   },
diff --git a/src/Auth0.js b/src/Auth0.js
deleted file mode 100644
index bdadd4f9f..000000000
--- a/src/Auth0.js
+++ /dev/null
@@ -1,573 +0,0 @@
-var util = require('util');
-
-var pkg = require('../package.json');
-var utils = require('./utils');
-var jsonToBase64 = utils.jsonToBase64;
-var ArgumentError = require('./exceptions').ArgumentError;
-var ClientsManager = require('./ClientsManager');
-var UsersManager = require('./UsersManager');
-var ConnectionsManager = require('./ConnectionsManager');
-var BlacklistedTokensManager = require('./BlacklistedTokensManager');
-var RulesManager = require('./RulesManager');
-var DeviceCredentialsManager = require('./DeviceCredentialsManager');
-var EmailProviderManager = require('./EmailProviderManager');
-var StatsManager = require('./StatsManager');
-var TenantManager = require('./TenantManager');
-var JobsManager = require('./JobsManager');
-var TicketsManager = require('./TicketsManager');
-
-var BASE_URL_FORMAT = 'https://%s/api/v2';
-
-
-/**
- * @class
- * Auth0 module.
- * @constructor
- *
- * @param   {Object}  options           Options for the Auth0 SDK.
- * @param   {String}  options.token     API access token.
- * @param   {String}  [options.domain]  Auth0 server domain.
- */
-var Auth0 = function (options) {
-  if (!options || typeof options !== 'object') {
-    throw new ArgumentError('Auth0 SDK options must be an object');
-  }
-
-  if (!options.token || options.token.length === 0) {
-    throw new ArgumentError('An access token must be provided');
-  }
-
-  if (!options.domain || options.domain.length === 0) {
-    throw new ArgumentError('Must provide a domain');
-  }
-
-  var managerOptions = {
-    headers: {
-      'Authorization': 'Bearer ' + options.token,
-      'User-agent': 'node.js/' + process.version.replace('v', ''),
-      'Content-Type': 'application/json'
-    },
-    baseUrl: util.format(BASE_URL_FORMAT, options.domain)
-  };
-
-  if (options.telemetry !== false) {
-    var telemetry = jsonToBase64(this.getAuth0ClientInfo());
-    managerOptions.headers['Auth0-Client'] = telemetry;
-  }
-
-  /**
-   * Simple abstraction for performing CRUD operations on the
-   * clients endpoint.
-   *
-   * @type {ClientsManager}
-   */
-  this.clients = new ClientsManager(managerOptions);
-
-  /**
-   * Simple abstraction for performing CRUD operations on the
-   * users endpoint.
-   *
-   * @type {UsersManager}
-   */
-  this.users = new UsersManager(managerOptions);
-
-  /**
-   * Simple abstraction for performing CRUD operations on the
-   * connections endpoint.
-   *
-   * @type {ConnectionsManager}
-   */
-  this.connections = new ConnectionsManager(managerOptions);
-
-  /**
-   * Simple abstraction for performing CRUD operations on the
-   * device credentials endpoint.
-   *
-   * @type {DeviceCredentialsManager}
-   */
-  this.deviceCredentials = new DeviceCredentialsManager(managerOptions);
-
-  /**
-   * Simple abstraction for performing CRUD operations on the
-   * rules endpoint.
-   *
-   * @type {RulesManager}
-   */
-  this.rules = new RulesManager(managerOptions);
-
-  /**
-   * Simple abstraction for performing CRUD operations on the
-   * blacklisted tokens endpoint.
-   *
-   * @type {BlacklistedtokensManager}
-   */
-  this.blacklistedTokens = new BlacklistedTokensManager(managerOptions);
-
-  /**
-   * Simple abstraction for performing CRUD operations on the
-   * email provider endpoint.
-   *
-   * @type {EmailProviderManager}
-   */
-  this.emailProvider = new EmailProviderManager(managerOptions);
-
-  /**
-   * Auth0 account statistics manager.
-   *
-   * @type {StatsManager}
-   */
-  this.stats = new StatsManager(managerOptions);
-
-  /**
-   * Auth0 tenant settings manager.
-   *
-   * @type {TenantManager}
-   */
-  this.tenant = new TenantManager(managerOptions);
-
-  /**
-   * Jobs manager.
-   *
-   * @type {JobsManager}
-   */
-  this.jobs = new JobsManager(managerOptions);
-
-  /**
-   * Tickets manager.
-   *
-   * @type {TicketsManager}
-   */
-  this.tickets = new TicketsManager(managerOptions);
-};
-
-
-/**
- * Return an object with information about the current client,
- *
- * @method
- * @memberOf Auth0
- *
- * @return {Object}   Object containing client information.
- */
-Auth0.prototype.getAuth0ClientInfo = function () {
-  var clientInfo = {
-    name: 'node-auth0',
-    version: pkg.version,
-    dependencies: [],
-    environment: [{
-      name: 'node.js',
-      version: process.version.replace('v', '')
-    }]
-  };
-
-  // Add the dependencies to the client info object.
-  Object
-    .keys(pkg.dependencies)
-    .forEach(function (name) {
-      clientInfo.dependencies.push({
-        name: name,
-        version: pkg.dependencies[name]
-      });
-    });
-
-  return clientInfo;
-};
-
-
-/**
- * Wrapper for auth0.connections.getAll()
- *
- * @method getConnections
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'getConnections', 'connections.getAll');
-
-
-/**
- * Wrapper for auth0.connections.create()
- *
- * @method createConnection
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'createConnection', 'connections.create');
-
-
-/**
- * Wrapper for auth0.connections.get()
- *
- * @method getConnection
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'getConnection', 'connections.get');
-
-
-/**
- * Wrapper for auth0.connections.delete()
- *
- * @method deleteConnection
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'deleteConnection', 'connections.delete');
-
-
-/**
- * Wrapper for auth0.connections.update()
- *
- * @method updateConnection
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'updateConnection', 'connections.update');
-
-
-/**
- * Wrapper for auth0.clients.getAll()
- *
- * @method getClients
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'getClients', 'clients.getAll');
-
-
-/**
- * Wrapper for auth0.clients.get()
- *
- * @method getClient
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'getClient', 'clients.get');
-
-
-/**
- * Wrapper for auth0.clients.create()
- *
- * @method createClient
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'createClient', 'clients.create');
-
-
-/**
- * Wrapper for auth0.clients.update()
- *
- * @method updateClient
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'updateClient', 'clients.update');
-
-
-/**
- * Wrapper for auth0.clients.delete()
- *
- * @method deleteClient
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'deleteClient', 'clients.delete');
-
-
-/**
- * Wrapper for auth0.deviceCredentials.createPubicKey()
- *
- * @method createDevicePublicKey
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'createDevicePublicKey', 'deviceCredentials.createPublicKey');
-
-
-/**
- * Wrapper for auth0.deviceCredentials.getAll()
- *
- * @method getDeviceCredentials
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'getDeviceCredentials', 'deviceCredentials.getAll');
-
-
-/**
- * Wrapper for auth0.deviceCredentials.delete()
- *
- * @method deleteDeviceCredential
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'deleteDeviceCredential', 'deviceCredentials.delete');
-
-
-/**
- * Wrapper for auth0.rules.getAll()
- *
- * @method getRules
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'getRules', 'rules.getAll');
-
-
-/**
- * Wrapper for auth0.rules.create()
- *
- * @method createRules
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'createRule', 'rules.create');
-
-
-/**
- * Wrapper for auth0.rules.get()
- *
- * @method getRule
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'getRule', 'rules.get');
-
-
-/**
- * Wrapper for auth0.rules.delete()
- *
- * @method deleteRule
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'deleteRule', 'rules.delete');
-
-
-/**
- * Wrapper for auth0.rules.update()
- *
- * @method updateRule
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'updateRule', 'rules.update');
-
-
-/**
- * Wrapper for auth0.users.getAll()
- *
- * @method getUsers
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'getUsers', 'users.getAll');
-
-
-/**
- * Wrapper for auth0.users.get()
- *
- * @method getUser
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'getUser', 'users.get');
-
-
-/**
- * Wrapper for auth0.users.deleteAll()
- *
- * @method deleteAllUsers
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'deleteAllUsers', 'users.deleteAll');
-
-
-/**
- * Wrapper for auth0.users.delete()
- *
- * @method deleteUser
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'deleteUser', 'users.delete');
-
-
-/**
- * Wrapper for auth0.users.create()
- *
- * @method createUser
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'createUser', 'users.create');
-
-
-/**
- * Wrapper for auth0.users.update()
- *
- * @method updateUser
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'updateUser', 'users.update');
-
-
-/**
- * Wrapper for auth0.users.updateUserMetadata()
- *
- * @method updateUserMetadata
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'updateUserMetadata', 'users.updateUserMetadata');
-
-
-/**
- * Wrapper for auth0.users.updateAppMetadata()
- *
- * @method updateAppMetadata
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'updateAppMetadata', 'users.updateAppMetadata');
-
-
-/**
- * Wrapper for auth0.users.deleteMultifactorProvider()
- *
- * @method deleteUserMultifactor
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'deleteUserMultifcator', 'users.deleteMultifactorProvider');
-
-
-/**
- * Wrapper for auth0.users.unlink()
- *
- * @method unlinkUsers
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'unlinkUsers', 'users.unlink');
-
-
-/**
- * Wrapper for auth0.users.link()
- *
- * @method linkUsers
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'linkUsers', 'users.link');
-
-
-/**
- * Wrapper for auth0.blacklistedTokens.getAll()
- *
- * @method getBlacklistedTokens
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'getBlacklistedTokens', 'blacklistedTokens.getAll');
-
-
-/**
- * Wrapper for auth0.blacklistedTokens.add()
- *
- * @method blacklistToken
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'blacklistToken', 'blacklistedTokens.add');
-
-
-/**
- * Wrapper for auth0.emailProvider.get()
- *
- * @method getEmailProvider
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'getEmailProvider', 'emailProvider.get');
-
-
-/**
- * Wrapper for auth0.emailProvider.configure()
- *
- * @method configureEmailProvider
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'configureEmailProvider', 'emailProvider.configure');
-
-
-/**
- * Wrapper for auth0.emailProvider.delete()
- *
- * @method deleteEmailProvider
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'deleteEmailProvider', 'emailProvider.delete');
-
-
-/**
- * Wrapper for auth0.emailProvider.update()
- *
- * @method updateEmailProvider
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'updateEmailProvider', 'emailProvider.update');
-
-
-/**
- * Wrapper for auth0.stats.getActiveUsersCount()
- *
- * @method getActiveUsersCount
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'getActiveUsersCount', 'stats.getActiveUsersCount');
-
-
-/**
- * Wrapper for auth0.stats.getDaily()
- *
- * @method getDailyStats
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'getDailyStats', 'stats.getDaily');
-
-
-/**
- * Wrapper for auth0.tenatn.getSettings()
- *
- * @method getTenantSettings
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'getTenantSettings', 'tenant.getSettings');
-
-
-/**
- * Wrapper for auth0.tenant.updateSettings()
- *
- * @method updateTenantSettings
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'updateTenantSettings', 'tenant.updateSettings');
-
-
-/**
- * Wrapper for auth0.jobs.get()
- *
- * @method getJob
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'getJob', 'jobs.get');
-
-
-/**
- * Wrapper for auth0.jobs.importUsers()
- *
- * @method importUsers
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'importUsers', 'jobs.importUsers');
-
-
-/**
- * Wrapper for auth0.jobs.verifyEmail()
- *
- * @method sendEmailVerification
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'sendEmailVerification', 'jobs.verifyEmail');
-
-
-/**
- * Wrapper for auth0.tickets.changePassword()
- *
- * @method createPasswordChangeTicket
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'createPasswordChangeTicket', 'tickets.changePassword');
-
-
-/**
- * Wrapper for auth0.tickets.verifyEmail()
- *
- * @method createEmailVerificationTicket
- * @memberOf Auth0
- */
-utils.wrapPropertyMethod(Auth0, 'createEmailVerificationTicket', 'tickets.verifyEmail');
-
-
-module.exports = Auth0;
diff --git a/src/auth/DatabaseAuthenticator.js b/src/auth/DatabaseAuthenticator.js
new file mode 100644
index 000000000..9c5701661
--- /dev/null
+++ b/src/auth/DatabaseAuthenticator.js
@@ -0,0 +1,173 @@
+var extend = require('util')._extend;
+
+var ArgumentError = require('../exceptions').ArgumentError;
+var RestClient = require('rest-facade').Client;
+
+
+/**
+ * @class
+ * Abstracts the sign-in, sign-up and change-password processes for Database &
+ * Active Directory auhtentication services.
+ * @constructor
+ *
+ * @param  {Object}              options            Authenticator options.
+ * @param  {String}              options.baseUrl    The auth0 account URL.
+ * @param  {String}              [options.clientId] Default client ID.
+ * @param  {OAuthAuthenticator}  oauth              OAuthAuthenticator instance.
+ */
+var DatabaseAuthenticator = function (options, oauth) {
+  if (!options) {
+    throw new ArgumentError('Missing authenticator options');
+  }
+
+  if (typeof options !== 'object') {
+    throw new ArgumentError('The authenticator options must be an object');
+  }
+
+  var dbConnectionsUrl = options.baseUrl + '/dbconnections/:type';
+
+  this.oauth = oauth;
+  this.dbConnections = new RestClient(dbConnectionsUrl);
+  this.clientId = options.clientId;
+};
+
+
+/**
+ * Sign in using a database or active directory service.
+ *
+ * @method    signIn
+ * @memberOf  DatabaseAuthenticator
+ *
+ * @param   {Object}    data              User credentials object.
+ * @param   {String}    data.username     Username.
+ * @param   {String}    data.password     User password.
+ * @param   {String}    data.connection   Identity provider in use.
+ * @param   {Function}  [cb]              Method callback.
+ *
+ * @return  {Promise|undefined}
+ */
+DatabaseAuthenticator.prototype.signIn = function (userData, cb) {
+  var defaultFields = {
+    connection: 'Username-Password-Authentication'
+  };
+  var data = extend(defaultFields, userData);
+
+  if (!userData || typeof userData !== 'object') {
+    throw new ArgumentError('Missing user data object');
+  }
+
+  if (typeof data.username !== 'string'
+      || data.username.trim().length === 0) {
+    throw new ArgumentError('username field is required');
+  }
+
+  if (typeof data.password !== 'string'
+      || data.password.trim().length === 0) {
+    throw new ArgumentError('password field is required');
+  }
+
+  return this.oauth.signIn(data, cb);
+};
+
+
+/**
+ * Sign up using a database or active directory service.
+ *
+ * @method    signUp
+ * @memberOf  DatabaseAuthenticator
+ *
+ * @param   {Object}    data              User credentials object.
+ * @param   {String}    data.email        User email address.
+ * @param   {String}    data.password     User password.
+ * @param   {Stinrg}    data.connection   Identity provider in use.
+ * @param   {Function}  [cb]              Method callback.
+ *
+ * @return  {Promise|undefined}
+ */
+DatabaseAuthenticator.prototype.signUp = function (userData, cb) {
+  var params = {
+    type: 'signup'
+  };
+  var defaultFields = {
+    client_id: this.clientId
+  };
+  var data = extend(defaultFields, userData);
+
+  if (!userData || typeof userData !== 'object') {
+    throw new ArgumentError('Missing user data object');
+  }
+
+  if (typeof data.email !== 'string'
+      || data.email.trim().length === 0) {
+    throw new ArgumentError('email field is required');
+  }
+
+  if (typeof data.password !== 'string'
+      || data.password.trim().length === 0) {
+    throw new ArgumentError('password field is required');
+  }
+
+  if (typeof data.connection !== 'string'
+      || data.connection.trim().length === 0) {
+    throw new ArgumentError('connection field is required');
+  }
+
+  if (cb && cb instanceof Function) {
+    return this.dbConnections.create(params, data, cb);
+  }
+
+  return this.dbConnections.create(params, data);
+};
+
+
+/**
+ * Change passwor using a database or active directory service.
+ *
+ * @method changePassword
+ * @memberOf DatabaseAuthenticator
+ *
+ * @param   {Object}    data              User credentials object.
+ * @param   {String}    data.email        User email address.
+ * @param   {String}    data.password     New password.
+ * @param   {String}    data.connection   Identity provider in use.
+ * @param   {Function}  [cb]              Method callback.
+ *
+ * @return  {Promise|undefined}
+ */
+DatabaseAuthenticator.prototype.changePassword = function (userData, cb) {
+  var params = {
+    type: 'change_password'
+  };
+  var defaultFields = {
+    client_id: this.clientId
+  };
+  var data = extend(defaultFields, userData);
+
+  if (!userData || typeof userData !== 'object') {
+    throw new ArgumentError('Missing user data object');
+  }
+
+  if (typeof data.email !== 'string'
+      || data.email.trim().length === 0) {
+    throw new ArgumentError('email field is required');
+  }
+
+  if (typeof data.password !== 'string'
+      || data.password.trim().length === 0) {
+    throw new ArgumentError('password field is required');
+  }
+
+  if (typeof data.connection !== 'string'
+      || data.connection.trim().length === 0) {
+    throw new ArgumentError('connection field is required');
+  }
+
+  if (cb && cb instanceof Function) {
+    return this.dbConnections.create(params, data, cb);
+  }
+
+  return this.dbConnections.create(params, data);
+};
+
+
+module.exports = DatabaseAuthenticator;
diff --git a/src/auth/OAuthAuthenticator.js b/src/auth/OAuthAuthenticator.js
new file mode 100644
index 000000000..8daf2afa4
--- /dev/null
+++ b/src/auth/OAuthAuthenticator.js
@@ -0,0 +1,116 @@
+var extend = require('util')._extend;
+
+var ArgumentError = require('../exceptions').ArgumentError;
+var RestClient = require('rest-facade').Client;
+
+
+/**
+ * @class
+ * Abstracts the sign-in, sign-up and change-password processes for Database &
+ * Active Directory auhtentication services.
+ * @constructor
+ *
+ * @param  {Object}              options            Authenticator options.
+ * @param  {String}              options.baseUrl    The auth0 account URL.
+ * @param  {String}              [options.clientId] Default client ID.
+ */
+var OAuthAuthenticator = function (options) {
+  if (!options) {
+    throw new ArgumentError('Missing authenticator options');
+  }
+
+  if (typeof options !== 'object') {
+    throw new ArgumentError('The authenticator options must be an object');
+  }
+
+  var oauthUrl = options.baseUrl + '/oauth/:type';
+
+  this.oauth = new RestClient(oauthUrl);
+  this.clientId = options.clientId;
+};
+
+
+/**
+ * Sign in using a username and password.
+ *
+ * @method    signIn
+ * @memberOf  OAuthAuthenticator
+ *
+ * @param   {Object}    userData              User credentials object.
+ * @param   {String}    userData.username     Username.
+ * @param   {String}    userData.password     User password.
+ * @param   {String}    userData.connection   The identity provider in use.
+ * @param   {Function}  [cb]                  Method callback.
+ *
+ * @return  {Promise|undefined}
+ */
+OAuthAuthenticator.prototype.signIn = function (userData, cb) {
+  var params = {
+    type: 'ro'
+  };
+  var defaultFields = {
+    client_id: this.clientId,
+    grant_type: 'password',
+    scope: 'openid'
+  };
+  var data = extend(defaultFields, userData);
+
+  if (!userData || typeof userData !== 'object') {
+    throw new ArgumentError('Missing user data object');
+  }
+
+  if (typeof data.connection !== 'string'
+      || data.connection.split().length === 0)
+  {
+    throw new ArgumentError('connection field is required');
+  }
+
+  if (cb && cb instanceof Function) {
+    return this.oauth.create(params, data, cb);
+  }
+
+  return this.oauth.create(params, data);
+};
+
+
+/**
+ * Sign in using a social provider access token.
+ *
+ * @method    socialSignIn
+ * @memberOf  OAuthAuthenticator
+ *
+ * @param   {Object}    data                User credentials object.
+ * @param   {String}    data.access_token   User access token.
+ * @param   {String}    data.connection     Identity provider.
+ * @param   {Function}  [cb]                  Method callback.
+ *
+ * @return  {Promise|undefined}
+ */
+OAuthAuthenticator.prototype.socialSignIn = function (data, cb) {
+  var params = {
+    type: 'access_token'
+  };
+
+  if (typeof data !== 'object') {
+    throw new ArgumentError('Missing user credential objects');
+  }
+
+  if (typeof data.access_token !== 'string'
+      || data.access_token.trim().length === 0) {
+    throw new ArgumentError('access_token field is required');
+  }
+
+  if (typeof data.connection !== 'string'
+      || data.connection.trim().length === 0) {
+    throw new ArgumentError('connection field is required');
+  }
+
+  if (cb && cb instanceof Function) {
+    return this.oauth.create(params, data, cb);
+  }
+
+  return this.oauth.create(params, data);
+};
+
+
+module.exports = OAuthAuthenticator;
diff --git a/src/auth/PasswordlessAuthenticator.js b/src/auth/PasswordlessAuthenticator.js
new file mode 100644
index 000000000..8383ccbd7
--- /dev/null
+++ b/src/auth/PasswordlessAuthenticator.js
@@ -0,0 +1,159 @@
+var extend = require('util')._extend;
+
+var ArgumentError = require('../exceptions').ArgumentError;
+var RestClient = require('rest-facade').Client;
+
+
+/**
+ * @class
+ * Handles authenticator with passwordless flows, e.g. SMS, Touch ID, etc.
+ * @constructor
+ *
+ * @param  {Object}              options            Authenticator options.
+ * @param  {String}              options.baseUrl    The auth0 account URL.
+ * @param  {String}              [options.clientId] Default client ID.
+ * @param  {OAuthAuthenticator}  oauth              OAuthAuthenticator instance.
+ */
+var PasswordlessAuthenticator = function (options, oauth) {
+  if (!options) {
+    throw new ArgumentError('Missing authenticator options');
+  }
+
+  if (typeof options !== 'object') {
+    throw new ArgumentError('The authenticator options must be an object');
+  }
+
+  var baseUrl = options.baseUrl + '/passwordless/start';
+
+  this.oauth = oauth;
+  this.passwordless = new RestClient(baseUrl);
+  this.clientId = options.clientId;
+};
+
+
+/**
+ * Sign in with the given user credentials.
+ *
+ * @method
+ * @memberOf PasswordlessAuthenticator
+ *
+ * @param   {Object}    userData              User credentials object.
+ * @param   {String}    userData.username     Username.
+ * @param   {String}    userData.password     Password.
+ * @param   {String}    [userData.client_id]  Client ID.
+ * @param   {Function}  [cb]                  Method callback.
+ *
+ * @return  {Promise|undefined}
+ */
+PasswordlessAuthenticator.prototype.signIn = function (userData, cb) {
+  var defaultFields = {
+    client_id: this.clientId
+  };
+  var data = extend(defaultFields, userData);
+
+  // Don't let the user override the connection nor the grant type.
+  data.connection = 'sms';
+  data.grant_type = 'password';
+
+  if (!userData || typeof userData !== 'object') {
+    throw new ArgumentError('Missing user data object');
+  }
+
+  if (typeof data.username !== 'string'
+      || data.username.trim().length === 0) {
+    throw new ArgumentError('username field (phone number) is required');
+  }
+
+  if (typeof data.password !== 'string'
+      || data.password.trim().length === 0) {
+    throw new ArgumentError('password field (verification code) is required');
+  }
+
+  return this.oauth.signIn(data, cb);
+};
+
+
+/**
+ * Start passwordless flow sending an email.
+ *
+ * @method
+ * @memberOf PasswordlessAuthenticator
+ *
+ * @param   {Object}    userData                User account data.
+ * @param   {String}    userData.email          User email address.
+ * @param   {String}    userData.send           The type of email to be sent.
+ * @param   {Function}  [cb]                    Method callback.
+ *
+ * @return  {Promise|undefined}
+ */
+PasswordlessAuthenticator.prototype.sendEmail = function (userData, cb) {
+  var defaultFields = {
+    client_id: this.clientId
+  };
+  var data = extend(defaultFields, userData);
+
+  // Don't let the user override the connection nor the grant type.
+  data.connection = 'email';
+
+  if (!userData || typeof userData !== 'object') {
+    throw new ArgumentError('Missing user data object');
+  }
+
+  if (typeof data.email !== 'string'
+      || data.email.trim().length === 0) {
+    throw new ArgumentError('email field is required');
+  }
+
+  if (typeof data.send !== 'string'
+      || data.send.trim().length === 0) {
+    throw new ArgumentError('send field is required');
+  }
+
+  if (cb && cb instanceof Function) {
+    return this.passwordless.create(data, cb);
+  }
+
+  return this.passwordless.create(data);
+};
+
+
+/**
+ * Start passwordless flow sending an SMS.
+ *
+ * @method
+ * @memberOf PasswordlessAuthenticator
+ *
+ * @param   {Object}    userData                User account data.
+ * @param   {String}    userData.phone_number   User phone number.
+ * @param   {String}    [userData.client_id]    Client ID.
+ * @param   {Function}  [cb]                    Method callback.
+ *
+ * @return  {Promise|undefined}
+ */
+PasswordlessAuthenticator.prototype.sendSMS = function (userData, cb) {
+  var defaultFields = {
+    client_id: this.clientId
+  };
+  var data = extend(defaultFields, userData);
+
+  // Don't let the user override the connection nor the grant type.
+  data.connection = 'sms';
+
+  if (!userData || typeof userData !== 'object') {
+    throw new ArgumentError('Missing user data object');
+  }
+
+  if (typeof data.phone_number !== 'string'
+      || data.phone_number.trim().length === 0) {
+    throw new ArgumentError('phone_number field is required');
+  }
+
+  if (cb && cb instanceof Function) {
+    return this.passwordless.create(data, cb);
+  }
+
+  return this.passwordless.create(data);
+};
+
+
+module.exports = PasswordlessAuthenticator;
diff --git a/src/auth/TokensManager.js b/src/auth/TokensManager.js
new file mode 100644
index 000000000..ffb0c4fc9
--- /dev/null
+++ b/src/auth/TokensManager.js
@@ -0,0 +1,138 @@
+var extend = require('util')._extend;
+var getRequestPromise = require('../utils').getRequestPromise;
+
+var ArgumentError = require('../exceptions').ArgumentError;
+
+
+/**
+ * @class
+ * Provides methods for getting token data and exchanging tokens.
+ * @constructor
+ *
+ * @param  {Object}   options               Manager options.
+ * @param  {String}   options.baseUrl       The auth0 account URL.
+ * @param  {String}   [options.headers]     Default request headers.
+ * @param  {String}   [options.clientId]    Default client ID.
+ */
+var TokensManager = function (options) {
+  if (typeof options !== 'object') {
+    throw new ArgumentError('Missing tokens manager options');
+  }
+
+  if (typeof options.baseUrl !== 'string') {
+    throw new ArgumentError('baseUrl field is required');
+  }
+
+  this.baseUrl = options.baseUrl;
+  this.headers = options.headers || {};
+  this.clientId = options.clientId || '';
+};
+
+
+/**
+ * Given an ID token get the user profile linked to it.
+ *
+ * @method
+ * @memberOf TokensManager
+ *
+ * @param   {String}    idToken     User ID token.
+ * @param   {Function}  [cb]        Method callback.
+ *
+ * @return  {Promise|undefined}
+ */
+TokensManager.prototype.getInfo = function (idToken, cb) {
+  var headers = extend({}, this.headers);
+
+  if (idToken === null || idToken === undefined) {
+    throw new ArgumentError('An ID token is required');
+  }
+
+  if (typeof idToken !== 'string' || idToken.trim().length === 0) {
+    throw new ArgumentError('The ID token is not valid');
+  }
+
+  // Perform the request.
+  var promise = getRequestPromise({
+    method: 'POST',
+    url: this.baseUrl + '/tokeninfo',
+    data: { id_token: idToken },
+    headers: headers
+  });
+
+  // Use callback if given.
+  if (cb instanceof Function) {
+    promise
+      .then(cb.bind(null, null))
+      .catch(cb);
+    return;
+  }
+
+  return promise;
+};
+
+
+/**
+ * Exchange the token of the logged in user with a token that is valid to call
+ * the API (signed with the API secret).
+ *
+ * @method
+ * @memberOf TokensManager
+ *
+ * @param   {Object}    data              Token data object.
+ * @param   {String}    data.id_token     User ID token.
+ * @param   {String}    data.target       Target client ID.
+ * @param   {String}    data.api_type     The API to be used (aws, auth0, etc).
+ * @param   {String}    data.grant_type   Grant type (password, jwt, etc).
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+TokensManager.prototype.getDelegationToken = function (data, cb) {
+  var body = extend({ client_id : this.clientId }, data);
+  var headers = this.headers;
+
+  if (!data) {
+    throw new ArgumentError('Missing token data object');
+  }
+
+  if (typeof data.id_token !== 'string'
+      || data.id_token.trim().length === 0) {
+    throw new ArgumentError('id_token field is required');
+  }
+
+  if (typeof data.target !== 'string'
+      || data.target.trim().length === 0) {
+    throw new ArgumentError('target field is required');
+  }
+
+  if (typeof data.api_type !== 'string'
+      || data.api_type.trim().length === 0) {
+    throw new ArgumentError('api_type field is required');
+  }
+
+  if (typeof data.grant_type !== 'string'
+      || data.grant_type.trim().length === 0) {
+    throw new ArgumentError('grant_type field is required');
+  }
+
+  // Perform the request.
+  var promise = getRequestPromise({
+    method: 'POST',
+    url: this.baseUrl + '/delegation',
+    data: body,
+    headers: headers
+  });
+
+  // Use callback if given.
+  if (cb instanceof Function) {
+    promise
+      .then(cb.bind(null, null))
+      .catch(cb);
+    return;
+  }
+
+  return promise;
+};
+
+
+module.exports = TokensManager;
diff --git a/src/auth/UsersManager.js b/src/auth/UsersManager.js
new file mode 100644
index 000000000..89503e222
--- /dev/null
+++ b/src/auth/UsersManager.js
@@ -0,0 +1,137 @@
+var extend = require('util')._extend;
+var getRequestPromise = require('../utils').getRequestPromise;
+
+var ArgumentError = require('../exceptions').ArgumentError;
+
+
+/**
+ * @class
+ * Provides methods for getting user information and impersonating users.
+ * @constructor
+ *
+ * @param  {Object}   options               Manager options.
+ * @param  {String}   options.baseUrl       The auth0 account URL.
+ * @param  {String}   [options.headers]     Default request headers.
+ * @param  {String}   [options.clientId]    Default client ID.
+ */
+var UsersManager = function (options) {
+  if (typeof options !== 'object') {
+    throw new ArgumentError('Missing users manager options');
+  }
+
+  if (typeof options.baseUrl !== 'string') {
+    throw new ArgumentError('baseUrl field is required');
+  }
+
+  this.baseUrl = options.baseUrl;
+  this.headers = options.headers;
+  this.clientId = options.clientId;
+};
+
+
+/**
+ * Given an access token get the user profile linked to it.
+ *
+ * @method
+ * @memberOf UsersManager
+ *
+ * @param   {String}    accessToken   User access token.
+ * @param   {Function}  [cb]          Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+UsersManager.prototype.getInfo = function (accessToken, cb) {
+  var url = this.baseUrl + '/userinfo';
+  var headers = extend({}, this.headers);
+
+  if (accessToken === null || accessToken === undefined) {
+    throw new ArgumentError('An access token is required');
+  }
+
+  if (typeof accessToken !== 'string' || accessToken.trim().length === 0) {
+    throw new ArgumentError('Invalid access token');
+  }
+
+  // Send the user access token in the Authorization header.
+  headers['Authorization'] = 'Bearer ' + accessToken;
+
+  // Perform the request.
+  var promise = getRequestPromise({
+    method: 'GET',
+    url: url,
+    headers: headers
+  });
+
+  // Use callback if given.
+  if (cb instanceof Function) {
+    promise
+      .then(cb.bind(null, null))
+      .catch(cb);
+    return;
+  }
+
+  return promise;
+};
+
+
+/**
+ * Impersonate the user with the given user ID.
+ *
+ * @method
+ * @memberOf UsersManager
+ *
+ * @param   {String}    userId                    User ID token.
+ * @param   {Object}    settings                  Impersonation settings.
+ * @param   {String}    settings.impersonator_id  Impersonator user ID.
+ * @param   {String}    settings.protocol         The authentication protocol.
+ * @param   {Function}  [cb]]                     Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+UsersManager.prototype.impersonate = function (userId, settings, cb) {
+  var url = this.baseUrl + '/users/' + userId + '/impersonate';
+  var data = extend({ client_id: this.clientId }, settings);
+
+  if (userId === null || userId === undefined) {
+    throw new ArgumentError('You must specify a user ID');
+  }
+
+  if (typeof userId !== 'string' || userId.trim().length === 0) {
+    throw new ArgumentError('The user ID is not valid');
+  }
+
+  if (typeof settings !== 'object') {
+    throw new ArgumentError('Missing impersonation settings object');
+  }
+
+  if (typeof settings.impersonator_id !== 'string'
+      || settings.impersonator_id.trim().length === 0) {
+    throw new ArgumentError('impersonator_id field is required');
+  }
+
+  if (typeof settings.protocol !== 'string'
+      || settings.protocol.trim().length === 0) {
+    throw new ArgumentError('protocol field is required');
+  }
+
+  // Perform the request.
+  var promise = getRequestPromise({
+    method: 'POST',
+    headers: this.headers,
+    data: data,
+    url: url
+  });
+
+  // Use callback if given.
+  if (cb instanceof Function) {
+    promise
+      .then(cb.bind(null, null))
+      .catch(cb);
+    return;
+  }
+
+  return promise;
+};
+
+
+module.exports = UsersManager;
diff --git a/src/auth/index.js b/src/auth/index.js
new file mode 100644
index 000000000..597e4e42c
--- /dev/null
+++ b/src/auth/index.js
@@ -0,0 +1,269 @@
+var util = require('util');
+
+var pkg = require('../../package.json');
+var utils = require('../utils');
+var jsonToBase64 = utils.jsonToBase64;
+var ArgumentError = require('../exceptions').ArgumentError;
+
+// Authenticators.
+var OAuthAuthenticator = require('./OAuthAuthenticator');
+var DatabaseAuthenticator = require('./DatabaseAuthenticator');
+var PasswordlessAuthenticator = require('./PasswordlessAuthenticator');
+
+// Managers
+var UsersManager = require('./UsersManager');
+var TokensManager = require('./TokensManager');
+
+var BASE_URL_FORMAT = 'https://%s';
+
+
+/**
+ * @class
+ * Authentication Client SDK module.
+ * @constructor
+ *
+ * @param   {Object}  options             Options for the Authentication Client SDK.
+ * @param   {String}  options.domain      AuthenticationClient server domain.
+ * @param   {String}  [options.clientId]  Default client ID.
+ */
+var AuthenticationClient = function (options) {
+  if (!options || typeof options !== 'object') {
+    throw new ArgumentError(
+      'Authentication Client SDK options must be an object'
+    );
+  }
+
+  if (!options.domain || options.domain.length === 0) {
+    throw new ArgumentError('Must provide a domain');
+  }
+
+  var managerOptions = {
+    clientId: options.clientId,
+    headers: {
+      'User-agent': 'node.js/' + process.version.replace('v', ''),
+      'Content-Type': 'application/json'
+    },
+    baseUrl: util.format(BASE_URL_FORMAT, options.domain)
+  };
+
+  if (options.telemetry !== false) {
+    var telemetry = jsonToBase64(this.getClientInfo());
+    managerOptions.headers['Auth0-Client'] = telemetry;
+  }
+
+  /**
+   * OAuth authenticator.
+   *
+   * @type {OAuthAuthenticator}
+   */
+  this.oauth = new OAuthAuthenticator(managerOptions);
+
+  /**
+   * Database authenticator.
+   *
+   * @type {DatabaseAuthenticator}
+   */
+  this.database = new DatabaseAuthenticator(managerOptions, this.oauth);
+
+  /**
+   * Passwordless authenticator.
+   *
+   * @type {PasswordlessAuthenticator}
+   */
+  this.passwordless = new PasswordlessAuthenticator(managerOptions, this.oauth);
+
+  /**
+   * Users manager.
+   *
+   * @type {UsersManager}
+   */
+  this.users = new UsersManager(managerOptions);
+
+  /**
+   * Tokens manager.
+   *
+   * @type {TokensManager}
+   */
+  this.tokens = new TokensManager(managerOptions);
+};
+
+
+/**
+ * Return an object with information about the current client,
+ *
+ * @method
+ * @memberOf AuthenticationClient
+ *
+ * @return {Object}   Object containing client information.
+ */
+AuthenticationClient.prototype.getClientInfo = function () {
+  var clientInfo = {
+    name: 'node-auth0',
+    version: pkg.version,
+    dependencies: [],
+    environment: [{
+      name: 'node.js',
+      version: process.version.replace('v', '')
+    }]
+  };
+
+  // Add the dependencies to the client info object.
+  Object
+    .keys(pkg.dependencies)
+    .forEach(function (name) {
+      clientInfo.dependencies.push({
+        name: name,
+        version: pkg.dependencies[name]
+      });
+    });
+
+  return clientInfo;
+};
+
+
+/**
+ * Binding for auth0.passwordless.sendEmail().
+ *
+ * @method
+ * @memberOf AuthenticationClient
+ *
+ * @param   {Object}  data              User data object.
+ * @param   {String}  data.email        User email address.
+ * @param   {Object}  [data.authParams] Authentication parameters.
+ *
+ * @return  {Promise|undefined}
+ */
+AuthenticationClient.prototype.requestMagicLink = function (data, cb) {
+  data.send = 'link';
+
+  return this.passwordless.sendEmail(data, cb);
+};
+
+
+/**
+ * Binding for auth0.passwordless.sendEmail().
+ *
+ * @method
+ * @memberOf AuthenticationClient
+ *
+ * @param   {Object}  data              User data object.
+ * @param   {String}  data.email        User email address.
+ * @param   {Object}  [data.authParams] Authentication parameters.
+ *
+ * @return  {Promise|undefined}
+ */
+AuthenticationClient.prototype.requestEmailCode = function (data, cb) {
+  data.send = 'code';
+
+  return this.passwordless.sendEmail(data, cb);
+};
+
+
+/**
+ * Binding for auth0.passwordless.sendSMS().
+ *
+ * @method
+ * @memberOf AuthenticationClient
+ *
+ * @param   {Object}  data                User data object.
+ * @param   {String}  data.phone_number   The user phone number.
+ *
+ * @return  {Promise|undefined}
+ */
+AuthenticationClient.prototype.requestSMSCode = function (data, cb) {
+  var translatedData = {
+    phone_number: data.phoneNumber || data.phone_number
+  };
+
+  return this.passwordless.sendSMS(translatedData, cb);
+};
+
+
+/**
+ * Binding for auth0.passwordless.login().
+ *
+ * @method
+ * @memberOf AuthenticationClient
+ *
+ * @param   {Object}  data              Credentials object.
+ * @param   {String}  data.username     Phone number.
+ * @param   {String}  data.password     Verification code.
+ * @param   {String}  data.target       Target client ID.
+ * @param   {String}  data.grant_type   Grant type.
+ *
+ * @return  {Promise|undefined}
+ */
+AuthenticationClient.prototype.verifySMSCode = function (data, cb) {
+  var translatedData = {
+    username: data.phoneNumber || data.phone_number || data.username,
+    password: data.code || data.password
+  };
+
+  return this.passwordless.signIn(translatedData, cb);
+};
+
+
+/**
+ * Binding for auth0.tokens.getDelegationToken().
+ *
+ * @method
+ * @memberOf  AuthenticationClient
+ *
+ * @param   {Object}  data              Token data object.
+ * @param   {String}  data.id_token     The user ID token.
+ * @param   {String}  data.api_type     The API type (aws, firebase, etc).
+ * @param   {String}  data.target       The target client ID.
+ * @param   {String}  data.grant_type   The grant type.
+ *
+ * @return  {Promise|undefined}
+ */
+AuthenticationClient.prototype.getDelegationToken = function (data, cb) {
+  var translatedData = {
+    id_token: data.id_token,
+    api_type: data.api || data.api_type,
+    scope: data.scope,
+    target: data.targetClientId || data.target
+  };
+
+  return this.tokens.getDelegationToken(translatedData, cb);
+};
+
+
+/**
+ * Binding for auth0.database.changePassword().
+ *
+ * @method
+ * @memberOf AuthenticationClient
+ *
+ * @param   {Object}    data            User data object.
+ * @param   {String}    data.email      User email.
+ * @param   {String}    data.password   User password.
+ * @param   {String}    data.connection Identity provider for the user.
+ *
+ * @return  {Promise|undefined}
+ */
+AuthenticationClient.prototype.changePassword = function (data, cb) {
+  var translatedData = {
+    connection: data.connection,
+    email: data.email || data.username,
+    password: data.password
+  };
+
+  return this.database.changePassword(data, cb);
+};
+
+
+/**
+ * Binding for auth0.users.getInfo().
+ *
+ * @method    getProfile
+ * @memberOf  AuthenticationClient
+ *
+ * @param     {String}  accessToken   The user access token.
+ *
+ * @return    {Promise|undefined}
+ */
+utils.wrapPropertyMethod(AuthenticationClient, 'getProfile', 'users.getInfo');
+
+
+module.exports = AuthenticationClient;
diff --git a/src/exceptions/APIError.js b/src/exceptions/APIError.js
deleted file mode 100644
index 3e3561e4e..000000000
--- a/src/exceptions/APIError.js
+++ /dev/null
@@ -1,11 +0,0 @@
-var util = require('util');
-
-var ApiError = function(statusCode, error, message){
-  this.error = error;
-  this.message = message;
-  this.statusCode = statusCode;
-};
-
-util.inherits(ApiError, Error);
-
-module.exports = ApiError;
diff --git a/src/index.js b/src/index.js
index 92ac13f38..14524d497 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,6 +1,4 @@
-var Auth0 = require('./Auth0');
-
-var exports = module.exports = function (options) {
-  return new Auth0(options);
+module.exports = {
+  ManagementClient: require('./management'),
+  AuthenticationClient: require('./auth')
 };
-
diff --git a/src/BlacklistedTokensManager.js b/src/management/BlacklistedTokensManager.js
similarity index 74%
rename from src/BlacklistedTokensManager.js
rename to src/management/BlacklistedTokensManager.js
index 28bbcb591..ef7d60375 100644
--- a/src/BlacklistedTokensManager.js
+++ b/src/management/BlacklistedTokensManager.js
@@ -1,6 +1,7 @@
 var RestClient = require('rest-facade').Client;
-var ArgumentError = require('./exceptions').ArgumentError;
-var utils = require('./utils');
+var ArgumentError = require('../exceptions').ArgumentError;
+var utils = require('../utils');
+
 
 /**
  * Simple facade for consuming a REST API endpoint.
@@ -8,15 +9,15 @@ var utils = require('./utils');
  * @see https://github.com/ngonzalvez/rest-facade
  */
 
+
 /**
  * @class BlacklistedTokensManager
  * Blacklisted Tokens Manager.
  * @constructor
  *
  * @param {Object} options            The client options.
- * @param {String} options.token      The API access token.
- * @param {String} [options.region]   The code for the region in use.
- * @param {String} [options.domain]   The API domain for the region in use.
+ * @param {String} options.baseUrl    The URL of the API.
+ * @param {Object} [options.headers]  Headers to be included in all requests.
  */
 var BlacklistedTokensManager = function (options) {
   if (options === null || typeof options !== 'object') {
@@ -50,21 +51,32 @@ var BlacklistedTokensManager = function (options) {
   this.resource = new RestClient(options.baseUrl + '/blacklists/tokens', clientOptions);
 };
 
+
 /**
  * Blacklist a new token.
  *
  * @method    create
  * @memberOf  BlacklistedTokensManager
- * @return    {Promise}
+ *
+ * @param   {Object}    token      Token data.
+ * @param   {String}    token.aud  Audience (your app client ID).
+ * @param   {String}    token.jti  The JWT ID claim.
+ * @param   {Function}  [cb]       Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(BlacklistedTokensManager, 'add', 'resource.create');
 
+
 /**
  * Get all blacklisted tokens.
  *
  * @method  getAll
  * @memberOf  BlacklistedTokensManager
- * @return  {Promise}               Returns a promise if no callback is received.
+ *
+ * @param   {Function}  [cb]    Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(BlacklistedTokensManager, 'getAll', 'resource.getAll');
 
diff --git a/src/ClientsManager.js b/src/management/ClientsManager.js
similarity index 60%
rename from src/ClientsManager.js
rename to src/management/ClientsManager.js
index f561920e6..7327e030c 100644
--- a/src/ClientsManager.js
+++ b/src/management/ClientsManager.js
@@ -1,6 +1,7 @@
 var RestClient = require('rest-facade').Client;
-var ArgumentError = require('./exceptions').ArgumentError;
-var utils = require('./utils');
+var ArgumentError = require('../exceptions').ArgumentError;
+var utils = require('../utils');
+
 
 /**
  * Simple facade for consuming a REST API endpoint.
@@ -8,15 +9,15 @@ var utils = require('./utils');
  * @see https://github.com/ngonzalvez/rest-facade
  */
 
+
 /**
  * @class ClientsManager
  * Auth0 Clients Manager.
  * @constructor
  *
  * @param {Object} options            The client options.
- * @param {String} options.token      The API access token.
- * @param {String} [options.region]   The code for the region in use.
- * @param {String} [options.domain]   The API domain for the region in use.
+ * @param {String} options.baseUrl    The URL of the API.
+ * @param {Object} [options.headers]  Headers to be included in all requests.
  */
 var ClientsManager = function (options) {
   if (options === null || typeof options !== 'object') {
@@ -36,7 +37,7 @@ var ClientsManager = function (options) {
    *
    * @type {Object}
    */
-  clientOptions = {
+  var clientOptions = {
     headers: options.headers,
     query: { repeatParams: false }
   };
@@ -50,48 +51,76 @@ var ClientsManager = function (options) {
   this.resource = new RestClient(options.baseUrl + '/clients/:client_id', clientOptions);
 };
 
+
 /**
  * Create an Auth0 client.
  *
  * @method    create
  * @memberOf  ClientsManager
- * @return    {Promise}
+ *
+ * @param   {Object}    data     The client data object.
+ * @param   {Function}  [cb]     Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(ClientsManager, 'create', 'resource.create');
 
+
 /**
  * Get all Auth0 clients.
  *
- * @method  getAll
+ * @method    getAll
  * @memberOf  ClientsManager
- * @return  {Promise}               Returns a promise if no callback is received.
+ *
+ * @param   {Function}  [cb]    Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(ClientsManager, 'getAll', 'resource.getAll');
 
+
 /**
  * Get an Auth0 client.
  *
- * @method  get
+ * @method    get
  * @memberOf  ClientsManager
- * @return  {Promise}
+ *
+ * @param   {Object}    params            Client parameters.
+ * @param   {String}    params.client_id  Application client ID.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(ClientsManager, 'get', 'resource.get');
 
+
 /**
  * Update an Auth0 client.
  *
  * @method    update
  * @memberOf  ClientsManager
- * @return    {Promise}
+ *
+ * @param   {Object}    params            Client parameters.
+ * @param   {String}    params.client_id  Application client ID.
+ * @param   {Object}    data              Updated client data.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return    {Promise|undefined}
  */
 utils.wrapPropertyMethod(ClientsManager, 'update', 'resource.patch');
 
+
 /**
  * Delete an Auth0 client.
  *
  * @method    delete
  * @memberOf  ClientsManager
- * @return    {Promise}
+ *
+ * @param   {Object}    params            Client parameters.
+ * @param   {String}    params.client_id  Application client ID.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(ClientsManager, 'delete', 'resource.delete');
 
diff --git a/src/ConnectionsManager.js b/src/management/ConnectionsManager.js
similarity index 63%
rename from src/ConnectionsManager.js
rename to src/management/ConnectionsManager.js
index adc088709..1719a3697 100644
--- a/src/ConnectionsManager.js
+++ b/src/management/ConnectionsManager.js
@@ -1,6 +1,7 @@
 var RestClient = require('rest-facade').Client;
-var ArgumentError = require('./exceptions').ArgumentError;
-var utils = require('./utils');
+var ArgumentError = require('../exceptions').ArgumentError;
+var utils = require('../utils');
+
 
 /**
  * Simple facade for consuming a REST API endpoint.
@@ -8,12 +9,15 @@ var utils = require('./utils');
  * @see https://github.com/ngonzalvez/rest-facade
  */
 
+
 /**
  * @class ConnectionsManager
  * Represents the relationship between Auth0 and an Iidentity provider.
  * @constructor
  *
- * @param   {Object}   options
+ * @param {Object} options            The client options.
+ * @param {String} options.baseUrl    The URL of the API.
+ * @param {Object} [options.headers]  Headers to be included in all requests.
  */
 var ConnectionsManager = function (options) {
   if (options === null || typeof options !== 'object') {
@@ -47,48 +51,76 @@ var ConnectionsManager = function (options) {
   this.resource = new RestClient(options.baseUrl + '/connections/:id ', apiOptions);
 };
 
+
 /**
  * Create a new connection.
  *
  * @method    create
  * @memberOf  ConnectionsManager
- * @return    {Promise}
+ *
+ * @param   {Object}    data     Connection data object.
+ * @param   {Function}  [cb]     Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(ConnectionsManager, 'create', 'resource.create');
 
+
 /**
  * Get all connections.
  *
  * @method  getAll
  * @memberOf  ConnectionsManager
- * @return  {Promise}               Returns a promise if no callback is received.
+ *
+ * @param   {Function}  [cb]    Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(ConnectionsManager, 'getAll', 'resource.getAll');
 
+
 /**
  * Get an Auth0 connection.
  *
  * @method  get
  * @memberOf  ConnectionsManager
- * @return  {Promise}
+ *
+ * @param   {Object}    params          Connection parameters.
+ * @param   {String}    params.id       Connection ID.
+ * @param   {Function}  [cb]            Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(ConnectionsManager, 'get', 'resource.get');
 
+
 /**
  * Update an existing connection.
  *
  * @method    update
  * @memberOf  ConnectionsManager
- * @return    {Promise}
+ *
+ * @param   {Object}    params        Conneciton parameters.
+ * @param   {String}    params.id     Connection ID.
+ * @param   {Object}    data          Updated connection data.
+ * @param   {Function}  [cb]          Callback function.
+ *
+ * @return    {Promise|undefined}
  */
 utils.wrapPropertyMethod(ConnectionsManager, 'update', 'resource.patch');
 
+
 /**
  * Delete an existing connection.
  *
  * @method    delete
  * @memberOf  ConnectionsManager
- * @return    {Promise}
+ *
+ * @param   {Object}    params          Connection parameters.
+ * @param   {String}    params.id       Connection ID.
+ * @param   {Function}  [cb]            Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(ConnectionsManager, 'delete', 'resource.delete');
 
diff --git a/src/DeviceCredentialsManager.js b/src/management/DeviceCredentialsManager.js
similarity index 70%
rename from src/DeviceCredentialsManager.js
rename to src/management/DeviceCredentialsManager.js
index 6a452f295..d818035d8 100644
--- a/src/DeviceCredentialsManager.js
+++ b/src/management/DeviceCredentialsManager.js
@@ -1,6 +1,7 @@
 var RestClient = require('rest-facade').Client;
-var ArgumentError = require('./exceptions').ArgumentError;
-var utils = require('./utils');
+var ArgumentError = require('../exceptions').ArgumentError;
+var utils = require('../utils');
+
 
 /**
  * Simple facade for consuming a REST API endpoint.
@@ -8,15 +9,15 @@ var utils = require('./utils');
  * @see https://github.com/ngonzalvez/rest-facade
  */
 
+
 /**
  * @class DeviceCredentialsManager
  * Manages Auth0 Device Credentials.
  * @constructor
  *
- * @param {Object} options            The credential options.
- * @param {String} options.token      The API access token.
- * @param {String} [options.region]   The code for the region in use.
- * @param {String} [options.domain]   The API domain for the region in use.
+ * @param {Object} options            The client options.
+ * @param {String} options.baseUrl    The URL of the API.
+ * @param {Object} [options.headers]  Headers to be included in all requests.
  */
 var DeviceCredentialsManager = function (options) {
   if (options === null || typeof options !== 'object') {
@@ -50,30 +51,45 @@ var DeviceCredentialsManager = function (options) {
   this.resource = new RestClient(options.baseUrl + '/device-credentials/:id', clientOptions);
 };
 
+
 /**
  * Create an Auth0 credential.
  *
  * @method    create
  * @memberOf  DeviceCredentialsManager
- * @return    {Promise}
+ *
+ * @param   {Object}    data     The device credential data object.
+ * @param   {Function}  [cb]     Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(DeviceCredentialsManager, 'createPublicKey', 'resource.create');
 
+
 /**
  * Get all Auth0 credentials.
  *
  * @method  getAll
  * @memberOf  DeviceCredentialsManager
- * @return  {Promise}               Returns a promise if no callback is received.
+ *
+ * @param   {Function}  [cb]    Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(DeviceCredentialsManager, 'getAll', 'resource.getAll');
 
+
 /**
  * Delete an Auth0 device credential.
  *
  * @method    delete
  * @memberOf  DeviceCredentialsManager
- * @return    {Promise}
+ *
+ * @param   {Object}    params          Credential parameters.
+ * @param   {String}    params.id       Device credential ID.
+ * @param   {Function}  [cb]            Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(DeviceCredentialsManager, 'delete', 'resource.delete');
 
diff --git a/src/EmailProviderManager.js b/src/management/EmailProviderManager.js
similarity index 72%
rename from src/EmailProviderManager.js
rename to src/management/EmailProviderManager.js
index 865b66185..8b03ffb75 100644
--- a/src/EmailProviderManager.js
+++ b/src/management/EmailProviderManager.js
@@ -1,6 +1,7 @@
 var RestClient = require('rest-facade').Client;
-var ArgumentError = require('./exceptions').ArgumentError;
-var utils = require('./utils');
+var ArgumentError = require('../exceptions').ArgumentError;
+var utils = require('../utils');
+
 
 /**
  * Simple facade for consuming a REST API endpoint.
@@ -8,15 +9,15 @@ var utils = require('./utils');
  * @see https://github.com/ngonzalvez/rest-facade
  */
 
+
 /**
  * @class EmailProviderManager
  * Auth0 Email Provider.
  * @constructor
  *
  * @param {Object} options            The client options.
- * @param {String} options.token      The API access token.
- * @param {String} [options.region]   The code for the region in use.
- * @param {String} [options.domain]   The API domain for the region in use.
+ * @param {String} options.baseUrl    The URL of the API.
+ * @param {Object} [options.headers]  Headers to be included in all requests.
  */
 var EmailProviderManager = function (options) {
   if (options === null || typeof options !== 'object') {
@@ -50,21 +51,30 @@ var EmailProviderManager = function (options) {
   this.resource = new RestClient(options.baseUrl + '/emails/provider', clientOptions);
 };
 
+
 /**
  * Configure the email provider.
  *
  * @method  configure
  * @memberOf  EmailProviderManager
- * @return  {Promise}               Returns a promise if no callback is received.
+ *
+ * @param   {Object}    data     The email provider data object.
+ * @param   {Function}  [cb]     Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(EmailProviderManager, 'configure', 'resource.create');
 
+
 /**
  * Get the email provider.
  *
  * @method  get
  * @memberOf  EmailProviderManager
- * @return  {Promise}               Returns a promise if no callback is received.
+ *
+ * @param   {Function}  [cb]    Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(EmailProviderManager, 'get', 'resource.getAll');
 
@@ -74,16 +84,24 @@ utils.wrapPropertyMethod(EmailProviderManager, 'get', 'resource.getAll');
  *
  * @method    update
  * @memberOf  EmailProviderManager
- * @return    {Promise}
+ *
+ * @param   {Object}    data              Updated email provider data.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return    {Promise|undefined}
  */
 utils.wrapPropertyMethod(EmailProviderManager, 'update', 'resource.patch');
 
+
 /**
  * Delete email provider.
  *
  * @method    delete
  * @memberOf  EmailProviderManager
- * @return    {Promise}
+ *
+ * @param   {Function}  [cb]    Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(EmailProviderManager, 'delete', 'resource.delete');
 
diff --git a/src/JobsManager.js b/src/management/JobsManager.js
similarity index 72%
rename from src/JobsManager.js
rename to src/management/JobsManager.js
index 88fc0f4d1..cadab5994 100644
--- a/src/JobsManager.js
+++ b/src/management/JobsManager.js
@@ -3,7 +3,14 @@ var extend = require('util')._extend;
 var Promise = require('bluebird');
 
 var RestClient = require('rest-facade').Client;
-var ArgumentError = require('./exceptions').ArgumentError;
+var ArgumentError = require('../exceptions').ArgumentError;
+
+
+/**
+ * Simple facade for consuming a REST API endpoint.
+ * @external RestClient
+ * @see https://github.com/ngonzalvez/rest-facade
+ */
 
 
 /**
@@ -11,7 +18,9 @@ var ArgumentError = require('./exceptions').ArgumentError;
  * Abstract the creation as well as the retrieval of async jobs.
  * @constructor
  *
- * @param {Object}    options       Manager options.
+ * @param {Object} options            The client options.
+ * @param {String} options.baseUrl    The URL of the API.
+ * @param {Object} [options.headers]  Headers to be included in all requests.
  */
 var JobsManager = function (options){
   if (options === null || typeof options !== 'object') {
@@ -46,9 +55,14 @@ var JobsManager = function (options){
 /**
  * Get a job by its ID.
  *
- * @method
- * @param   {Function}  [cb]  Callback function.
- * @return  {Promise}
+ * @method   get
+ * @memberOf JobsManager
+ *
+ * @param   {Object}    params        Job parameters.
+ * @param   {String}    params.id     Job ID.
+ * @param   {Function}  [cb]          Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 JobsManager.prototype.get = function (params, cb) {
   if (!params.id || typeof params.id !== 'string') {
@@ -68,13 +82,15 @@ JobsManager.prototype.get = function (params, cb) {
  * Given a path to a file and a connection id, create a new job that imports the
  * users contained in the file and associate them with the given connection.
  *
- * @method
+ * @method   importUsers
+ * @memberOf JobsManager
+ *
  * @param   {Object}    data                Users import data.
- * @param   {String}    data.connectionId   The connection id of the connection
- *                                          to which users will be inserted.
- * @param   {String}    data.users          Path to file containing the users.
+ * @param   {String}    data.connectionId   Connection for the users insertion.
+ * @param   {String}    data.users          Path to the users data file.
  * @param   {Function}  [cb]                Callback function.
- * @return  {Promise}
+ *
+ * @return  {Promise|undefined}
  */
 JobsManager.prototype.importUsers = function (data, cb) {
   var options = this.options;
@@ -115,9 +131,14 @@ JobsManager.prototype.importUsers = function (data, cb) {
 /**
  * Send a verification email to a user.
  *
- * @method
- * @param   {Function}  [cb]  Callback function.
- * @return  {Promise}
+ * @method    verifyEmail
+ * @memberOf  JobsManager
+ *
+ * @param   {Object}    data          User data object.
+ * @param   {String}    data.user_id  ID of the user to be verified.
+ * @param   {Function}  [cb]          Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 JobsManager.prototype.verifyEmail = function (data, cb) {
   if (!data.user_id || typeof data.user_id !== 'string') {
diff --git a/src/RulesManager.js b/src/management/RulesManager.js
similarity index 62%
rename from src/RulesManager.js
rename to src/management/RulesManager.js
index 351fe210c..3d6538fbc 100644
--- a/src/RulesManager.js
+++ b/src/management/RulesManager.js
@@ -1,6 +1,7 @@
 var RestClient = require('rest-facade').Client;
-var ArgumentError = require('./exceptions').ArgumentError;
-var utils = require('./utils');
+var ArgumentError = require('../exceptions').ArgumentError;
+var utils = require('../utils');
+
 
 /**
  * Simple facade for consuming a REST API endpoint.
@@ -8,13 +9,16 @@ var utils = require('./utils');
  * @see https://github.com/ngonzalvez/rest-facade
  */
 
+
 /**
  * @class RulesManager
  * The rule class provides a simple abstraction for performing CRUD operations
  * on Auth0 RulesManagers.
  * @constructor
  *
- * @param   {Object}   options
+ * @param {Object} options            The client options.
+ * @param {String} options.baseUrl    The URL of the API.
+ * @param {Object} [options.headers]  Headers to be included in all requests.
  */
 var RulesManager = function (options) {
   if (options === null || typeof options !== 'object') {
@@ -34,7 +38,7 @@ var RulesManager = function (options) {
    *
    * @type {Object}
    */
-  apiOptions = {
+  var apiOptions = {
     headers: options.headers,
     query: { repeatParams: false }
   };
@@ -48,48 +52,76 @@ var RulesManager = function (options) {
   this.resource = new RestClient(options.baseUrl + '/rules/:id ', apiOptions);
 };
 
+
 /**
  * Create a new rule.
  *
  * @method    create
  * @memberOf  RulesManager
- * @return    {Promise}
+ *
+ * @param   {Object}    data     Rule data object.
+ * @param   {Function}  [cb]     Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(RulesManager, 'create', 'resource.create');
 
+
 /**
  * Get all rules.
  *
  * @method  getAll
  * @memberOf  RulesManager
- * @return  {Promise}               Returns a promise if no callback is received.
+ *
+ * @param   {Function}  [cb]     Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(RulesManager, 'getAll', 'resource.getAll');
 
+
 /**
  * Get an Auth0 rule.
  *
  * @method  get
  * @memberOf  RulesManager
- * @return  {Promise}
+ *
+ * @param   {Object}    params        Rule parameters.
+ * @param   {String}    params.id     Rule ID.
+ * @param   {Function}  [cb]          Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(RulesManager, 'get', 'resource.get');
 
+
 /**
  * Update an existing rule.
  *
  * @method    update
  * @memberOf  RulesManager
- * @return    {Promise}
+ *
+ * @param   {Object}    params        Rule parameters.
+ * @param   {String}    params.id     Rule ID.
+ * @param   {Object}    data          Updated rule data.
+ * @param   {Function}  [cb]          Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(RulesManager, 'update', 'resource.patch');
 
+
 /**
  * Delete an existing rule.
  *
  * @method    delete
  * @memberOf  RulesManager
- * @return    {Promise}
+ *
+ * @param   {Object}    params        Rule parameters.
+ * @param   {String}    params.id     Rule ID.
+ * @param   {Function}  [cb]          Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 utils.wrapPropertyMethod(RulesManager, 'delete', 'resource.delete');
 
diff --git a/src/StatsManager.js b/src/management/StatsManager.js
similarity index 65%
rename from src/StatsManager.js
rename to src/management/StatsManager.js
index 500e93998..cb5ee1235 100644
--- a/src/StatsManager.js
+++ b/src/management/StatsManager.js
@@ -1,11 +1,22 @@
 var RestClient = require('rest-facade').Client;
-var ArgumentError = require('./exceptions').ArgumentError;
+var ArgumentError = require('../exceptions').ArgumentError;
+
+
+/**
+ * Simple facade for consuming a REST API endpoint.
+ * @external RestClient
+ * @see https://github.com/ngonzalvez/rest-facade
+ */
 
 
 /**
  * @class
  * Abstracts interaction with the stats endpoint.
  * @constructor
+ *
+ * @param {Object} options            The client options.
+ * @param {String} options.baseUrl    The URL of the API.
+ * @param {Object} [options.headers]  Headers to be included in all requests.
  */
 var StatsManager = function (options){
   if (options === null || typeof options !== 'object') {
@@ -34,12 +45,19 @@ var StatsManager = function (options){
   this.stats = new RestClient(options.baseUrl + '/stats/:type', clientOptions);
 };
 
+
 /**
  * Get the daily stats.
  *
- * @method
- * @param   {Function}  [cb]  Callback function.
- * @return  {Promise}
+ * @method    getDaily
+ * @memberOf  StatsManager
+ *
+ * @param   {Object}    params        Stats parameters.
+ * @param   {String}    params.from   The first day in YYYYMMDD format.
+ * @param   {String}    params.to     The last day in YYYYMMDD format.
+ * @param   {Function}  [cb]          Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 StatsManager.prototype.getDaily = function (params, cb) {
   params = params || {};
@@ -52,12 +70,16 @@ StatsManager.prototype.getDaily = function (params, cb) {
   return this.stats.get(params);
 };
 
+
 /**
  * Get a the active users count.
  *
- * @method
+ * @method    getActiveUsersCount
+ * @memberOf  StatsManager
+ *
  * @param   {Function}  [cb]  Callback function.
- * @return  {Promise}         User retrieval promise.
+ *
+ * @return  {Promise|undefined}
  */
 StatsManager.prototype.getActiveUsersCount = function (cb) {
   var options = { type: 'active-users' };
diff --git a/src/TenantManager.js b/src/management/TenantManager.js
similarity index 71%
rename from src/TenantManager.js
rename to src/management/TenantManager.js
index e2f91659e..3532358c3 100644
--- a/src/TenantManager.js
+++ b/src/management/TenantManager.js
@@ -1,11 +1,22 @@
 var RestClient = require('rest-facade').Client;
-var ArgumentError = require('./exceptions').ArgumentError;
+var ArgumentError = require('../exceptions').ArgumentError;
+
+
+/**
+ * Simple facade for consuming a REST API endpoint.
+ * @external RestClient
+ * @see https://github.com/ngonzalvez/rest-facade
+ */
 
 
 /**
  * @class
  * Abstracts interaction with the tenant endpoint.
  * @constructor
+ *
+ * @param {Object} options            The client options.
+ * @param {String} options.baseUrl    The URL of the API.
+ * @param {Object} [options.headers]  Headers to be included in all requests.
  */
 var TenantManager = function (options){
   if (options === null || typeof options !== 'object') {
@@ -37,9 +48,13 @@ var TenantManager = function (options){
 /**
  * Update the tenant settings.
  *
- * @method
+ * @method    updateSettings
+ * @memberOf  TenantManager
+ *
+ * @param   {Object}    data  The new tenant settings.
  * @param   {Function}  [cb]  Callback function.
- * @return  {Promise}
+ *
+ * @return  {Promise|undefined}
  */
 TenantManager.prototype.updateSettings = function (data, cb) {
   if (cb && cb instanceof Function) {
@@ -53,9 +68,12 @@ TenantManager.prototype.updateSettings = function (data, cb) {
 /**
  * Get the tenant settings..
  *
- * @method
+ * @method    getSettings
+ * @memberOf  TenantManager
+ *
  * @param   {Function}  [cb]  Callback function.
- * @return  {Promise}
+ *
+ * @return  {Promise|undefined}
  */
 TenantManager.prototype.getSettings = function (cb) {
   if (cb && cb instanceof Function) {
diff --git a/src/TicketsManager.js b/src/management/TicketsManager.js
similarity index 96%
rename from src/TicketsManager.js
rename to src/management/TicketsManager.js
index 96bbeeb7d..f3ad4eeb5 100644
--- a/src/TicketsManager.js
+++ b/src/management/TicketsManager.js
@@ -1,5 +1,5 @@
 var RestClient = require('rest-facade').Client;
-var ArgumentError = require('./exceptions').ArgumentError;
+var ArgumentError = require('../exceptions').ArgumentError;
 
 
 /**
diff --git a/src/UsersManager.js b/src/management/UsersManager.js
similarity index 55%
rename from src/UsersManager.js
rename to src/management/UsersManager.js
index 7e344f74f..9de472edd 100644
--- a/src/UsersManager.js
+++ b/src/management/UsersManager.js
@@ -1,11 +1,22 @@
 var RestClient = require('rest-facade').Client;
-var ArgumentError = require('./exceptions').ArgumentError;
+var ArgumentError = require('../exceptions').ArgumentError;
+
+
+/**
+ * Simple facade for consuming a REST API endpoint.
+ * @external RestClient
+ * @see https://github.com/ngonzalvez/rest-facade
+ */
 
 
 /**
  * @class
  * Abstracts interaction with the users endpoint.
  * @constructor
+ *
+ * @param {Object} options            The client options.
+ * @param {String} options.baseUrl    The URL of the API.
+ * @param {Object} [options.headers]  Headers to be included in all requests.
  */
 var UsersManager = function (options){
   if (options === null || typeof options !== 'object') {
@@ -47,10 +58,13 @@ var UsersManager = function (options){
 /**
  * Create a new user.
  *
- * @method
+ * @method    create
+ * @memberOf  UsersManager
+ *
  * @param   {Object}    data    User data.
  * @param   {Function}  [cb]    Callback function.
- * @return  {Promise}           User creation promise.
+ *
+ * @return  {Promise|undefined}
  */
 UsersManager.prototype.create = function (data, cb) {
   if (cb && cb instanceof Function) {
@@ -64,11 +78,21 @@ UsersManager.prototype.create = function (data, cb) {
 /**
  * Get all users.
  *
- * @method
- * @param   {Function}  [cb]  Callback function.
- * @return  {Promise}         UsersManager retrieval promise.
+ * @method    getAll
+ * @memberOf  UsersManager
+ *
+ * @param   {Object}    [params]          Users params.
+ * @param   {Number}    [params.per_page] Number of users per page.
+ * @param   {Number}    [params.page]     Page number.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return  {Promise|undefined}
  */
-UsersManager.prototype.getAll = function () {
+UsersManager.prototype.getAll = function (params) {
+  if (typeof params === 'object' && typeof params.q === 'string') {
+    params.q = encodeURIComponent(params.q);
+  }
+
   return this.users.getAll.apply(this.users, arguments);
 };
 
@@ -76,10 +100,14 @@ UsersManager.prototype.getAll = function () {
 /**
  * Get a user by its id.
  *
- * @method
- * @param   {any}       id    The user id.
- * @param   {Function}  [cb]  Callback function.
- * @return  {Promise}         User retrieval promise.
+ * @method    get
+ * @memberOf  UsersManager
+ *
+ * @param   {Object}    data      The user data object.
+ * @param   {String}    data.id   The user id.
+ * @param   {Function}  [cb]      Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 UsersManager.prototype.get = function () {
   return this.users.get.apply(this.users, arguments);
@@ -89,11 +117,15 @@ UsersManager.prototype.get = function () {
 /**
  * Update a user by its id.
  *
- * @method
- * @param   {any}       params  The user id.
- * @param   {Object}    data    New user data.
- * @param   {Function}  [cb]    Callback function
- * @return  {Promise}           User update promise.
+ * @method    update
+ * @memberOf  UsersManager
+ *
+ * @param   {Object}    params      The user parameters.
+ * @param   {String}    params.id   The user id.
+ * @param   {Object}    data        New user data.
+ * @param   {Function}  [cb]        Callback function
+ *
+ * @return  {Promise|undefined}
  */
 UsersManager.prototype.update = function () {
   return this.users.patch.apply(this.users, arguments);
@@ -103,11 +135,15 @@ UsersManager.prototype.update = function () {
 /**
  * Update the user metadata.
  *
- * @method
- * @param   {any}       params  The user id.
- * @param   {Object}    data    New user metadata.
- * @param   {Function}  [cb]    Callback function
- * @return  {Promise}
+ * @method    updateUserMetadata
+ * @memberOf  UsersManager
+ *
+ * @param   {Object}    params      The user data object..
+ * @param   {String}    params.id   The user id.
+ * @param   {Object}    metadata    New user metadata.
+ * @param   {Function}  [cb]        Callback function
+ *
+ * @return  {Promise|undefined}
  */
 UsersManager.prototype.updateUserMetadata = function (params, metadata, cb) {
   var data = {
@@ -123,13 +159,17 @@ UsersManager.prototype.updateUserMetadata = function (params, metadata, cb) {
 
 
 /**
- * Update the user metadata.
+ * Update the app metadata.
+ *
+ * @method    updateAppMetadata
+ * @memberOf  UsersManager
  *
- * @method
- * @param   {any}       params  The user id.
- * @param   {Object}    data    New user metadata.
- * @param   {Function}  [cb]    Callback function
- * @return  {Promise}
+ * @param   {Object}    params      The user data object..
+ * @param   {String}    params.id   The user id.
+ * @param   {Object}    metadata    New app metadata.
+ * @param   {Function}  [cb]        Callback function
+ *
+ * @return  {Promise|undefined}
  */
 UsersManager.prototype.updateAppMetadata = function (params, metadata, cb) {
   var data = {
@@ -147,11 +187,17 @@ UsersManager.prototype.updateAppMetadata = function (params, metadata, cb) {
 /**
  * Delete a user by its id.
  *
- * @method
- * @return  {Promise}           User delete promise.
+ * @method    delete
+ * @memberOf  UsersManager
+ *
+ * @param   {Object}    params      The user data object..
+ * @param   {String}    params.id   The user id.
+ * @param   {Function}  [cb]        Callback function
+ *
+ * @return  {Promise|undefined}
  */
 UsersManager.prototype.delete = function (params) {
-  if (typeof params !== 'object' || Number.isNaN(params.id)) {
+  if (typeof params !== 'object' || typeof params.id !== 'string') {
     throw new ArgumentError('You must provide an id for the delete method');
   }
 
@@ -162,8 +208,12 @@ UsersManager.prototype.delete = function (params) {
 /**
  * Delete all users.
  *
- * @method
- * @return  {Promise}
+ * @method    deleteAll
+ * @memberOf  UsersManager
+ *
+ * @param   {Function}  [cb]        Callback function
+ *
+ * @return  {Promise|undefined}
  */
 UsersManager.prototype.deleteAll = function (cb) {
   if (typeof cb !== 'function') {
@@ -179,8 +229,15 @@ UsersManager.prototype.deleteAll = function (cb) {
 /**
  * Delete a multifactor provider.
  *
- * @method
- * @return  {Promise}
+ * @method    deleteMultifactorProvider
+ * @memberOf  UsersManager
+ *
+ * @param   {Object}    params            Data object.
+ * @param   {String}    params.id         The user id.
+ * @param   {String}    params.provider   Multifactor provider.
+ * @param   {Function}  [cb]              Callback function
+ *
+ * @return  {Promise|undefined}
  */
 UsersManager.prototype.deleteMultifactorProvider = function (params, cb) {
   params = params || {};
@@ -204,8 +261,16 @@ UsersManager.prototype.deleteMultifactorProvider = function (params, cb) {
 /**
  * Link the user with another account.
  *
- * @method
- * @return  {Promise}
+ * @method    link
+ * @memberOf  UsersManager
+ *
+ * @param   {String}    userId                ID of the primary user.
+ * @param   {Object}    params                Secondary user data.
+ * @param   {String}    params.user_id        ID of the user to be linked.
+ * @param   {String}    params.connection_id  ID of the connection to be used.
+ * @param   {Function}  [cb]                  Callback function.
+ *
+ * @return  {Promise|undefined}
  */
 UsersManager.prototype.link = function (userId, params, cb) {
   var query = { id: userId };
@@ -227,8 +292,16 @@ UsersManager.prototype.link = function (userId, params, cb) {
 /**
  * Unlink the given accounts.
  *
- * @method
- * @return {Promise}
+ * @method    unlink
+ * @memberOf  UsersManager
+ *
+ * @param   {Object}    params            Linked users data.
+ * @param   {String}    params.id         Primary user ID.
+ * @param   {String}    params.provider   Identity provider in use.
+ * @param   {String}    params.user_id    Secondary user ID.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return {Promise|undefined}
  */
 UsersManager.prototype.unlink = function (params, cb) {
   params = params || {};
diff --git a/src/management/index.js b/src/management/index.js
new file mode 100644
index 000000000..a1e834637
--- /dev/null
+++ b/src/management/index.js
@@ -0,0 +1,575 @@
+var util = require('util');
+
+var pkg = require('../../package.json');
+var utils = require('../utils');
+var jsonToBase64 = utils.jsonToBase64;
+var ArgumentError = require('../exceptions').ArgumentError;
+
+// Managers.
+var ClientsManager = require('./ClientsManager');
+var UsersManager = require('./UsersManager');
+var ConnectionsManager = require('./ConnectionsManager');
+var BlacklistedTokensManager = require('./BlacklistedTokensManager');
+var RulesManager = require('./RulesManager');
+var DeviceCredentialsManager = require('./DeviceCredentialsManager');
+var EmailProviderManager = require('./EmailProviderManager');
+var StatsManager = require('./StatsManager');
+var TenantManager = require('./TenantManager');
+var JobsManager = require('./JobsManager');
+var TicketsManager = require('./TicketsManager');
+
+var BASE_URL_FORMAT = 'https://%s/api/v2';
+
+
+/**
+ * @class
+ * Management API SDK.
+ * @constructor
+ *
+ * @param   {Object}  options           Options for the ManagementClient SDK.
+ * @param   {String}  options.token     API access token.
+ * @param   {String}  [options.domain]  ManagementClient server domain.
+ */
+var ManagementClient = function (options) {
+  if (!options || typeof options !== 'object') {
+    throw new ArgumentError('Management API SDK options must be an object');
+  }
+
+  if (!options.token || options.token.length === 0) {
+    throw new ArgumentError('An access token must be provided');
+  }
+
+  if (!options.domain || options.domain.length === 0) {
+    throw new ArgumentError('Must provide a domain');
+  }
+
+  var managerOptions = {
+    headers: {
+      'Authorization': 'Bearer ' + options.token,
+      'User-agent': 'node.js/' + process.version.replace('v', ''),
+      'Content-Type': 'application/json'
+    },
+    baseUrl: util.format(BASE_URL_FORMAT, options.domain)
+  };
+
+  if (options.telemetry !== false) {
+    var telemetry = jsonToBase64(this.getClientInfo());
+    managerOptions.headers['Auth0-Client'] = telemetry;
+  }
+
+  /**
+   * Simple abstraction for performing CRUD operations on the
+   * clients endpoint.
+   *
+   * @type {ClientsManager}
+   */
+  this.clients = new ClientsManager(managerOptions);
+
+  /**
+   * Simple abstraction for performing CRUD operations on the
+   * users endpoint.
+   *
+   * @type {UsersManager}
+   */
+  this.users = new UsersManager(managerOptions);
+
+  /**
+   * Simple abstraction for performing CRUD operations on the
+   * connections endpoint.
+   *
+   * @type {ConnectionsManager}
+   */
+  this.connections = new ConnectionsManager(managerOptions);
+
+  /**
+   * Simple abstraction for performing CRUD operations on the
+   * device credentials endpoint.
+   *
+   * @type {DeviceCredentialsManager}
+   */
+  this.deviceCredentials = new DeviceCredentialsManager(managerOptions);
+
+  /**
+   * Simple abstraction for performing CRUD operations on the
+   * rules endpoint.
+   *
+   * @type {RulesManager}
+   */
+  this.rules = new RulesManager(managerOptions);
+
+  /**
+   * Simple abstraction for performing CRUD operations on the
+   * blacklisted tokens endpoint.
+   *
+   * @type {BlacklistedtokensManager}
+   */
+  this.blacklistedTokens = new BlacklistedTokensManager(managerOptions);
+
+  /**
+   * Simple abstraction for performing CRUD operations on the
+   * email provider endpoint.
+   *
+   * @type {EmailProviderManager}
+   */
+  this.emailProvider = new EmailProviderManager(managerOptions);
+
+  /**
+   * ManagementClient account statistics manager.
+   *
+   * @type {StatsManager}
+   */
+  this.stats = new StatsManager(managerOptions);
+
+  /**
+   * ManagementClient tenant settings manager.
+   *
+   * @type {TenantManager}
+   */
+  this.tenant = new TenantManager(managerOptions);
+
+  /**
+   * Jobs manager.
+   *
+   * @type {JobsManager}
+   */
+  this.jobs = new JobsManager(managerOptions);
+
+  /**
+   * Tickets manager.
+   *
+   * @type {TicketsManager}
+   */
+  this.tickets = new TicketsManager(managerOptions);
+};
+
+
+/**
+ * Return an object with information about the current client,
+ *
+ * @method
+ * @memberOf ManagementClient
+ *
+ * @return {Object}   Object containing client information.
+ */
+ManagementClient.prototype.getClientInfo = function () {
+  var clientInfo = {
+    name: 'node-auth0',
+    version: pkg.version,
+    dependencies: [],
+    environment: [{
+      name: 'node.js',
+      version: process.version.replace('v', '')
+    }]
+  };
+
+  // Add the dependencies to the client info object.
+  Object
+    .keys(pkg.dependencies)
+    .forEach(function (name) {
+      clientInfo.dependencies.push({
+        name: name,
+        version: pkg.dependencies[name]
+      });
+    });
+
+  return clientInfo;
+};
+
+
+/**
+ * Binding for auth0.connections.getAll()
+ *
+ * @method getConnections
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getConnections', 'connections.getAll');
+
+
+/**
+ * Binding for auth0.connections.create()
+ *
+ * @method createConnection
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'createConnection', 'connections.create');
+
+
+/**
+ * Binding for auth0.connections.get()
+ *
+ * @method getConnection
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getConnection', 'connections.get');
+
+
+/**
+ * Binding for auth0.connections.delete()
+ *
+ * @method deleteConnection
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'deleteConnection', 'connections.delete');
+
+
+/**
+ * Binding for auth0.connections.update()
+ *
+ * @method updateConnection
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'updateConnection', 'connections.update');
+
+
+/**
+ * Binding for auth0.clients.getAll()
+ *
+ * @method getClients
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getClients', 'clients.getAll');
+
+
+/**
+ * Binding for auth0.clients.get()
+ *
+ * @method getClient
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getClient', 'clients.get');
+
+
+/**
+ * Binding for auth0.clients.create()
+ *
+ * @method createClient
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'createClient', 'clients.create');
+
+
+/**
+ * Binding for auth0.clients.update()
+ *
+ * @method updateClient
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'updateClient', 'clients.update');
+
+
+/**
+ * Binding for auth0.clients.delete()
+ *
+ * @method deleteClient
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'deleteClient', 'clients.delete');
+
+
+/**
+ * Binding for auth0.deviceCredentials.createPubicKey()
+ *
+ * @method createDevicePublicKey
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'createDevicePublicKey', 'deviceCredentials.createPublicKey');
+
+
+/**
+ * Binding for auth0.deviceCredentials.getAll()
+ *
+ * @method getDeviceCredentials
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getDeviceCredentials', 'deviceCredentials.getAll');
+
+
+/**
+ * Binding for auth0.deviceCredentials.delete()
+ *
+ * @method deleteDeviceCredential
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'deleteDeviceCredential', 'deviceCredentials.delete');
+
+
+/**
+ * Binding for auth0.rules.getAll()
+ *
+ * @method getRules
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getRules', 'rules.getAll');
+
+
+/**
+ * Binding for auth0.rules.create()
+ *
+ * @method createRules
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'createRule', 'rules.create');
+
+
+/**
+ * Binding for auth0.rules.get()
+ *
+ * @method getRule
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getRule', 'rules.get');
+
+
+/**
+ * Binding for auth0.rules.delete()
+ *
+ * @method deleteRule
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'deleteRule', 'rules.delete');
+
+
+/**
+ * Binding for auth0.rules.update()
+ *
+ * @method updateRule
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'updateRule', 'rules.update');
+
+
+/**
+ * Binding for auth0.users.getAll()
+ *
+ * @method getUsers
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getUsers', 'users.getAll');
+
+
+/**
+ * Binding for auth0.users.get()
+ *
+ * @method getUser
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getUser', 'users.get');
+
+
+/**
+ * Binding for auth0.users.deleteAll()
+ *
+ * @method deleteAllUsers
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'deleteAllUsers', 'users.deleteAll');
+
+
+/**
+ * Binding for auth0.users.delete()
+ *
+ * @method deleteUser
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'deleteUser', 'users.delete');
+
+
+/**
+ * Binding for auth0.users.create()
+ *
+ * @method createUser
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'createUser', 'users.create');
+
+
+/**
+ * Binding for auth0.users.update()
+ *
+ * @method updateUser
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'updateUser', 'users.update');
+
+
+/**
+ * Binding for auth0.users.updateUserMetadata()
+ *
+ * @method updateUserMetadata
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'updateUserMetadata', 'users.updateUserMetadata');
+
+
+/**
+ * Binding for auth0.users.updateAppMetadata()
+ *
+ * @method updateAppMetadata
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'updateAppMetadata', 'users.updateAppMetadata');
+
+
+/**
+ * Binding for auth0.users.deleteMultifactorProvider()
+ *
+ * @method deleteUserMultifactor
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'deleteUserMultifcator', 'users.deleteMultifactorProvider');
+
+
+/**
+ * Binding for auth0.users.unlink()
+ *
+ * @method unlinkUsers
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'unlinkUsers', 'users.unlink');
+
+
+/**
+ * Binding for auth0.users.link()
+ *
+ * @method linkUsers
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'linkUsers', 'users.link');
+
+
+/**
+ * Binding for auth0.blacklistedTokens.getAll()
+ *
+ * @method getBlacklistedTokens
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getBlacklistedTokens', 'blacklistedTokens.getAll');
+
+
+/**
+ * Binding for auth0.blacklistedTokens.add()
+ *
+ * @method blacklistToken
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'blacklistToken', 'blacklistedTokens.add');
+
+
+/**
+ * Binding for auth0.emailProvider.get()
+ *
+ * @method getEmailProvider
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getEmailProvider', 'emailProvider.get');
+
+
+/**
+ * Binding for auth0.emailProvider.configure()
+ *
+ * @method configureEmailProvider
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'configureEmailProvider', 'emailProvider.configure');
+
+
+/**
+ * Binding for auth0.emailProvider.delete()
+ *
+ * @method deleteEmailProvider
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'deleteEmailProvider', 'emailProvider.delete');
+
+
+/**
+ * Binding for auth0.emailProvider.update()
+ *
+ * @method updateEmailProvider
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'updateEmailProvider', 'emailProvider.update');
+
+
+/**
+ * Binding for auth0.stats.getActiveUsersCount()
+ *
+ * @method getActiveUsersCount
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getActiveUsersCount', 'stats.getActiveUsersCount');
+
+
+/**
+ * Binding for auth0.stats.getDaily()
+ *
+ * @method getDailyStats
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getDailyStats', 'stats.getDaily');
+
+
+/**
+ * Binding for auth0.tenatn.getSettings()
+ *
+ * @method getTenantSettings
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getTenantSettings', 'tenant.getSettings');
+
+
+/**
+ * Binding for auth0.tenant.updateSettings()
+ *
+ * @method updateTenantSettings
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'updateTenantSettings', 'tenant.updateSettings');
+
+
+/**
+ * Binding for auth0.jobs.get()
+ *
+ * @method getJob
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getJob', 'jobs.get');
+
+
+/**
+ * Binding for auth0.jobs.importUsers()
+ *
+ * @method importUsers
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'importUsers', 'jobs.importUsers');
+
+
+/**
+ * Binding for auth0.jobs.verifyEmail()
+ *
+ * @method sendEmailVerification
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'sendEmailVerification', 'jobs.verifyEmail');
+
+
+/**
+ * Binding for auth0.tickets.changePassword()
+ *
+ * @method createPasswordChangeTicket
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'createPasswordChangeTicket', 'tickets.changePassword');
+
+
+/**
+ * Binding for auth0.tickets.verifyEmail()
+ *
+ * @method createEmailVerificationTicket
+ * @memberOf ManagementClient
+ */
+utils.wrapPropertyMethod(ManagementClient, 'createEmailVerificationTicket', 'tickets.verifyEmail');
+
+
+module.exports = ManagementClient;
diff --git a/src/utils.js b/src/utils.js
index 09fe2cffc..9f62e181a 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -1,3 +1,7 @@
+var Promise = require('bluebird');
+var request = require('superagent');
+
+
 /**
  * @module utils
  */
@@ -11,7 +15,13 @@ var utils = module.exports = {};
  * @memberOf utils
  */
 utils.jsonToBase64 = function (json) {
-  return (new Buffer(JSON.stringify(json))).toString('base64');
+  var bytes = new Buffer(JSON.stringify(json));
+
+  return bytes
+    .toString('base64')
+    .replace(/\+/g, '-')
+    .replace(/\//g, '_')
+    .replace(/=+$/, '');
 };
 
 
@@ -36,3 +46,35 @@ utils.wrapPropertyMethod = function (Parent, name, propertyMethod) {
   });
 }
 
+
+/**
+ * Perform a request with the given settings and return a promise that resolves
+ * when the request is successfull and rejects when there's an error.
+ *
+ * @method
+ * @memberOf utils
+ */
+utils.getRequestPromise = function (settings) {
+  return new Promise(function (resolve, reject) {
+    var method = settings.method.toLowerCase();
+    var req = request[method](settings.url);
+
+    for (var name in settings.headers) {
+      req = req.set(name, settings.headers[name]);
+    }
+
+    if (typeof settings.data === 'object') {
+      req = req.send(settings.data);
+    }
+
+    req.end(function (err, res) {
+      if (err) {
+        reject(err);
+        return;
+      }
+
+      resolve(res.body);
+    });
+  });
+};
+


Test patch:
diff --git a/test/auth/authentication-client.tests.js b/test/auth/authentication-client.tests.js
new file mode 100644
index 000000000..2afa96bc6
--- /dev/null
+++ b/test/auth/authentication-client.tests.js
@@ -0,0 +1,80 @@
+var expect = require('chai').expect;
+
+var ArgumentError = require('../../src/exceptions').ArgumentError;
+
+var AuthenticationClient = require('../../src/auth');
+var OAuthAuthenticator = require('../../src/auth/OAuthAuthenticator');
+var DatabaseAuthenticator = require('../../src/auth/DatabaseAuthenticator');
+var PasswordlessAuthenticator = require('../../src/auth/PasswordlessAuthenticator');
+var UsersManager = require('../../src/auth/UsersManager');
+var TokensManager = require('../../src/auth/TokensManager');
+
+var ensureProperty = require('../utils').ensureProperty;
+
+
+describe('AuthenticationClient', function () {
+
+  describe('#constructor', function () {
+    it('should raise an error when no options object is provided', function () {
+      expect(AuthenticationClient)
+        .to.throw(ArgumentError, 'Authentication Client SDK options must be an object');
+    });
+
+
+    it('should raise an error when the domain is not valid', function () {
+      var client = AuthenticationClient.bind(null, { token: 'token', domain: '' });
+
+      expect(client)
+        .to.throw(ArgumentError, 'Must provide a domain');
+    });
+  });
+
+
+  describe('instance properties', function () {
+    var properties = {
+      'OAuthAuthenticator': {
+        name: 'oauth',
+        cls: OAuthAuthenticator
+      },
+      'DatabaseAuthenticator': {
+        name: 'database',
+        cls: DatabaseAuthenticator
+      },
+      'PasswordlessAuthenticator': {
+        name: 'passwordless',
+        cls: PasswordlessAuthenticator
+      },
+      'UsersManager': {
+        name: 'users',
+        cls: UsersManager
+      },
+      'TokensManager': {
+        name: 'tokens',
+        cls: TokensManager
+      }
+    };
+    var options = {
+      clientId: 'CLIENT_ID',
+      domain: 'tenant.auth0.com'
+    };
+    var client = new AuthenticationClient(options);
+
+    // Tests common to all properties.
+    for (var name in properties) {
+      var property = properties[name];
+
+      it('should expose an instance of ' + name, ensureProperty(client, property.name, property.cls));
+    }
+  });
+
+
+  describe('instance methods', function () {
+    var method;
+    var methods = [];
+    var client = new AuthenticationClient({ token: 'token', domain: 'auth0.com' });
+
+    methods.forEach(function (method) {
+      ensureMethod(client, method);
+    });
+  });
+});
diff --git a/test/auth/database-auth.tests.js b/test/auth/database-auth.tests.js
new file mode 100644
index 000000000..a6c496769
--- /dev/null
+++ b/test/auth/database-auth.tests.js
@@ -0,0 +1,563 @@
+var expect = require('chai').expect;
+var extend = require('util')._extend;
+var nock = require('nock');
+
+// Constants.
+var SRC_DIR = '../../src';
+var DOMAIN = 'tenant.auth0.com';
+var API_URL = 'https://' + DOMAIN;
+var CLIENT_ID = 'TEST_CLIENT_ID';
+
+var ArgumentError = require(SRC_DIR + '/exceptions').ArgumentError;
+var Authenticator = require(SRC_DIR + '/auth/DatabaseAuthenticator');
+var OAuth = require(SRC_DIR + '/auth/OAuthAuthenticator');
+
+var validOptions = {
+  baseUrl: API_URL,
+  clientId: CLIENT_ID
+};
+
+
+describe('DatabaseAuthenticator', function () {
+
+  afterEach(function () {
+    nock.cleanAll();
+  });
+
+
+  describe('#constructor', function () {
+    it('should require an options object', function () {
+      expect(Authenticator)
+        .to.throw(ArgumentError, 'Missing authenticator options');
+
+      expect(Authenticator.bind(null, 1))
+        .to.throw(ArgumentError, 'The authenticator options must be an object');
+
+      expect(Authenticator.bind(null, validOptions))
+        .to.not.throw(ArgumentError);
+    });
+  });
+
+
+  describe('instance', function () {
+    var methods = ['signIn', 'signUp', 'changePassword'];
+    var oauth = new OAuth(validOptions);
+    var authenticator = new Authenticator(validOptions, oauth);
+
+    methods.forEach(function (method) {
+      it('should have a ' + method + ' method', function () {
+        expect(authenticator[method])
+          .to.exist
+          .to.be.an.instanceOf(Function);
+      });
+    });
+  });
+
+
+  describe('#signIn', function () {
+    var path = '/oauth/ro';
+    var userData = {
+      username: 'username',
+      password: 'pwd'
+    };
+
+    beforeEach(function () {
+      var oauth = new OAuth(validOptions);
+      this.authenticator = new Authenticator(validOptions, oauth);
+      this.request = nock(API_URL)
+        .post(path)
+        .reply(200);
+    });
+
+
+    it('should require an object as first argument', function () {
+      expect(this.authenticator.signIn)
+        .to.throw(ArgumentError, 'Missing user data object');
+    });
+
+
+    it('should require a username', function () {
+      var auth = this.authenticator;
+      var userData = { password: 'password' };
+      var signIn = auth.signIn.bind(auth, userData);
+
+      expect(signIn)
+        .to.throw(ArgumentError, 'username field is required');
+    });
+
+
+    it('should require a password', function () {
+      var auth = this.authenticator;
+      var userData = { username: 'username' };
+      var signIn = auth.signIn.bind(auth, userData);
+
+      expect(signIn)
+        .to.throw(ArgumentError, 'password field is required');
+    });
+
+
+    it('should accept a callback', function (done) {
+      this
+        .authenticator
+        .signIn(userData, done.bind(null, null));
+    });
+
+
+    it('should return a promise when no callback is provided', function (done) {
+      this
+        .authenticator
+        .signIn(userData)
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+
+    it('should perform a POST request to ' + path, function (done) {
+      var request = this.request;
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should include the user data in the request', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          for (var property in userData) {
+            if (userData[property] !== body[property]) {
+              return false;
+            }
+          }
+
+          return true;
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should include the Auth0 client ID in the request', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.client_id === CLIENT_ID;
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should use Username-Password-Authentication by default', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.connection === 'Username-Password-Authentication';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should allow the user to specify the connection', function (done) {
+      nock.cleanAll();
+
+      var data = extend({ connection: 'TEST_CONNECTION' }, userData);
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.connection === 'TEST_CONNECTION';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(data)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should use password as default grant type', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.grant_type === 'password';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should allow the user to specify the grant type', function (done) {
+      nock.cleanAll();
+
+      var data = extend({ grant_type: 'TEST_GRANT' }, userData);
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.grant_type === 'TEST_GRANT';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(data)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should use the openid scope by default', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.scope === 'openid';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+  });
+
+
+  describe('#signUp', function () {
+    var path = '/dbconnections/signup';
+    var userData = {
+      email: 'test@domain.com',
+      password: 'pwd',
+      connection: 'TEST_CONNECTION'
+    };
+
+    beforeEach(function () {
+      this.authenticator = new Authenticator(validOptions);
+      this.request = nock(API_URL)
+        .post(path)
+        .reply(200);
+    });
+
+
+    it('should require an object as first argument', function () {
+      expect(this.authenticator.signUp)
+        .to.throw(ArgumentError, 'Missing user data object');
+    });
+
+
+    it('should require an email', function () {
+      var auth = this.authenticator;
+      var userData = { password: 'password' };
+      var signUp = auth.signUp.bind(auth, userData);
+
+      expect(signUp)
+        .to.throw(ArgumentError, 'email field is required');
+    });
+
+
+    it('should require a password', function () {
+      var auth = this.authenticator;
+      var userData = { email: 'email@domain.com' };
+      var signUp = auth.signUp.bind(auth, userData);
+
+      expect(signUp)
+        .to.throw(ArgumentError, 'password field is required');
+    });
+
+
+    it('should require a connection', function () {
+      var auth = this.authenticator;
+      var userData = { email: 'email@domain.com', password: 'test' };
+      var signUp = auth.signUp.bind(auth, userData);
+
+      expect(signUp)
+        .to.throw(ArgumentError, 'connection field is required');
+    });
+
+
+    it('should accept a callback', function (done) {
+      this
+        .authenticator
+        .signUp(userData, done.bind(null, null));
+    });
+
+
+    it('should return a promise when no callback is provided', function (done) {
+      this
+        .authenticator
+        .signUp(userData)
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+
+    it('should perform a POST request to ' + path, function (done) {
+      var request = this.request;
+
+      this
+        .authenticator
+        .signUp(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should include the user data in the request', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          for (var property in userData) {
+            if (userData[property] !== body[property]) {
+              return false;
+            }
+          }
+
+          return true;
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signUp(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should include the Auth0 client ID in the request', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.client_id === CLIENT_ID;
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signUp(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+  });
+
+
+  describe('#changePassword', function () {
+    var path = '/dbconnections/change_password';
+    var userData = {
+      email: 'test@domain.com',
+      password: 'newPwd',
+      connection: 'TEST_CONNECTION'
+    };
+
+    beforeEach(function () {
+      this.authenticator = new Authenticator(validOptions);
+      this.request = nock(API_URL)
+        .post(path)
+        .reply(200);
+    });
+
+
+    it('should require an object as first argument', function () {
+      expect(this.authenticator.changePassword)
+        .to.throw(ArgumentError, 'Missing user data object');
+    });
+
+
+    it('should require an email', function () {
+      var auth = this.authenticator;
+      var userData = { password: 'password' };
+      var changePassword = auth.changePassword.bind(auth, userData);
+
+      expect(changePassword)
+        .to.throw(ArgumentError, 'email field is required');
+    });
+
+
+    it('should require a password', function () {
+      var auth = this.authenticator;
+      var userData = { email: 'email@domain.com' };
+      var changePassword = auth.changePassword.bind(auth, userData);
+
+      expect(changePassword)
+        .to.throw(ArgumentError, 'password field is required');
+    });
+
+
+    it('should require a connection', function () {
+      var auth = this.authenticator;
+      var userData = { email: 'email@domain.com', password: 'test' };
+      var changePassword = auth.changePassword.bind(auth, userData);
+
+      expect(changePassword)
+        .to.throw(ArgumentError, 'connection field is required');
+    });
+
+
+    it('should accept a callback', function (done) {
+      this
+        .authenticator
+        .changePassword(userData, done.bind(null, null));
+    });
+
+
+    it('should return a promise when no callback is provided', function (done) {
+      this
+        .authenticator
+        .changePassword(userData)
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+
+    it('should perform a POST request to ' + path, function (done) {
+      var request = this.request;
+
+      this
+        .authenticator
+        .changePassword(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should include the user data in the request', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          for (var property in userData) {
+            if (userData[property] !== body[property]) {
+              return false;
+            }
+          }
+
+          return true;
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .changePassword(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should include the Auth0 client ID in the request', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.client_id === CLIENT_ID;
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .changePassword(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+  });
+
+});
diff --git a/test/auth/oauth.tests.js b/test/auth/oauth.tests.js
new file mode 100644
index 000000000..962e17b21
--- /dev/null
+++ b/test/auth/oauth.tests.js
@@ -0,0 +1,468 @@
+var expect = require('chai').expect;
+var extend = require('util')._extend;
+var nock = require('nock');
+var Promise = require('bluebird');
+
+// Constants.
+var SRC_DIR = '../../src';
+var DOMAIN = 'tenant.auth0.com';
+var API_URL = 'https://' + DOMAIN;
+var CLIENT_ID = 'TEST_CLIENT_ID';
+
+var ArgumentError = require(SRC_DIR + '/exceptions').ArgumentError;
+var Authenticator = require(SRC_DIR + '/auth/OAuthAuthenticator');
+
+var validOptions = {
+  baseUrl: API_URL,
+  clientId: CLIENT_ID
+};
+
+
+describe('OAuthAuthenticator', function () {
+
+  afterEach(function () {
+    nock.cleanAll();
+  });
+
+
+  describe('#constructor', function () {
+    it('should require an options object', function () {
+      expect(Authenticator)
+        .to.throw(ArgumentError, 'Missing authenticator options');
+
+      expect(Authenticator.bind(null, 1))
+        .to.throw(ArgumentError, 'The authenticator options must be an object');
+
+      expect(Authenticator.bind(null, validOptions))
+        .to.not.throw(ArgumentError);
+    });
+  });
+
+
+  describe('instance', function () {
+    var methods = ['signIn', 'socialSignIn'];
+    var authenticator = new Authenticator(validOptions);
+
+    methods.forEach(function (method) {
+      it('should have a ' + method + ' method', function () {
+        expect(authenticator[method])
+          .to.exist
+          .to.be.an.instanceOf(Function);
+      });
+    });
+  });
+
+
+  describe('#signIn', function () {
+    var path = '/oauth/ro';
+    var userData = {
+      username: 'username',
+      password: 'pwd',
+      connection: 'Username-Password-Authentication'
+    };
+
+    beforeEach(function () {
+      this.authenticator = new Authenticator(validOptions);
+      this.request = nock(API_URL)
+        .post(path)
+        .reply(200);
+    });
+
+
+    it('should require an object as first argument', function () {
+      expect(this.authenticator.signIn)
+        .to.throw(ArgumentError, 'Missing user data object');
+    });
+
+
+    it('should require a connection', function () {
+      var auth = this.authenticator;
+      var signIn = auth.signIn.bind(auth, {});
+
+      expect(signIn)
+        .to.throw(ArgumentError, 'connection field is required');
+    });
+
+
+    it('should accept a callback', function (done) {
+      this
+        .authenticator
+        .signIn(userData, done.bind(null, null));
+    });
+
+
+    it('should return a promise when no callback is provided', function (done) {
+      this
+        .authenticator
+        .signIn(userData)
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+
+    it('should perform a POST request to ' + path, function (done) {
+      var request = this.request;
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should include the user data un the request', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          for (var property in userData) {
+            if (userData[property] !== body[property]) {
+              return false;
+            }
+          }
+
+          return true;
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should include the Auth0 client ID in the request', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.client_id === CLIENT_ID;
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should allow the user to specify the connection', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.connection === 'Username-Password-Authentication';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should use password as default grant type', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.grant_type === 'password';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should allow the user to specify the grant type', function (done) {
+      nock.cleanAll();
+
+      var data = extend({ grant_type: 'TEST_GRANT' }, userData);
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.grant_type === 'TEST_GRANT';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(data)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should use the openid scope by default', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.scope === 'openid';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+  });
+
+
+  describe('#socialSignIn', function () {
+    var path = '/oauth/access_token';
+    var userData = {
+      access_token: 'TEST_ACCESS_TOKEN',
+      connection: 'facebook'
+    };
+
+    beforeEach(function () {
+      this.authenticator = new Authenticator(validOptions);
+      this.request = nock(API_URL)
+        .post(path)
+        .reply(200);
+    });
+
+
+    it('should require an object as first argument', function () {
+      var auth = this.authenticator;
+      var socialSignIn = auth.socialSignIn.bind(auth);
+      var message = 'Missing user credential objects';
+
+      expect(socialSignIn)
+        .to.throw(ArgumentError, message);
+
+      expect(socialSignIn.bind(auth, userData))
+        .to.not.throw(ArgumentError, message);
+    });
+
+
+    it('should require an access token', function () {
+      var auth = this.authenticator;
+      var socialSignIn = auth.socialSignIn.bind(auth, {});
+      var message = 'access_token field is required';
+
+      expect(socialSignIn)
+        .to.throw(ArgumentError, message);
+    });
+
+
+    it('should require a connection', function () {
+      var auth = this.authenticator;
+      var data = {
+        access_token: userData.access_token
+      };
+      var socialSignIn = auth.socialSignIn.bind(auth, data);
+      var message = 'connection field is required';
+
+      expect(socialSignIn)
+        .to.throw(ArgumentError, message);
+    });
+
+
+    it('should require a connection', function () {
+      var auth = this.authenticator;
+      var data = {
+        access_token: userData.access_token
+      };
+      var socialSignIn = auth.socialSignIn.bind(auth, data);
+      var message = 'connection field is required';
+
+      expect(socialSignIn)
+        .to.throw(ArgumentError, message);
+    });
+
+
+    it('should accept a callback', function (done) {
+      this
+        .authenticator
+        .socialSignIn(userData, done.bind(null, null));
+    });
+
+
+    it('should return a promise when no callback is given', function () {
+      var returnValue = this.authenticator.socialSignIn(userData);
+
+      expect(returnValue)
+        .to.be.an.instanceOf(Promise);
+    });
+
+
+    it('should not return a promise when a callback is given', function () {
+      var cb = function () {};
+      var returnValue = this.authenticator.socialSignIn(userData, cb);
+
+      expect(returnValue)
+        .to.be.undefined;
+    });
+
+
+    it('should perform a POST request to ' + path, function (done) {
+      var request = this.request;
+
+      this
+        .authenticator
+        .socialSignIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+
+
+    it('should use the default client ID if none specified', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path)
+        .query({ client_id: CLIENT_ID })
+        .reply(200);
+
+      this
+        .authenticator
+        .socialSignIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+
+
+    it('should allow the user to specify a custom client ID', function (done) {
+      nock.cleanAll();
+
+      var data = extend({}, userData);
+      var request = nock(API_URL)
+        .post(path)
+        .query({ client_id: 'OVERRIDEN_ID' })
+        .reply(200);
+
+      data.client_id = 'OVERRIDEN_ID';
+
+      this
+        .authenticator
+        .socialSignIn(data)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+
+
+    it('should use the openid scope by default', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path)
+        .query({ scope: 'openid' })
+        .reply(200);
+
+      this
+        .authenticator
+        .socialSignIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+
+
+    it('should allow the user to specify the scope', function (done) {
+      nock.cleanAll();
+
+      var data = extend({}, userData);
+      var request = nock(API_URL)
+        .post(path)
+        .query({ scope: 'openid name email' })
+        .reply(200);
+
+      data.scope = 'openid name email';
+
+      this
+        .authenticator
+        .socialSignIn(data)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+
+
+    it('should use application/json as Content-Type', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path)
+        .matchHeader('Content-Type', 'application/json')
+        .reply(200);
+
+      this
+        .authenticator
+        .socialSignIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+  });
+
+});
diff --git a/test/auth/passwordless.tests.js b/test/auth/passwordless.tests.js
new file mode 100644
index 000000000..9d6eed319
--- /dev/null
+++ b/test/auth/passwordless.tests.js
@@ -0,0 +1,622 @@
+var expect = require('chai').expect;
+var extend = require('util')._extend;
+var nock = require('nock');
+
+// Constants.
+var SRC_DIR = '../../src';
+var DOMAIN = 'tenant.auth0.com';
+var API_URL = 'https://' + DOMAIN;
+var CLIENT_ID = 'TEST_CLIENT_ID';
+
+var ArgumentError = require(SRC_DIR + '/exceptions').ArgumentError;
+var Authenticator = require(SRC_DIR + '/auth/PasswordlessAuthenticator');
+var OAuth = require(SRC_DIR + '/auth/OAuthAuthenticator');
+
+var validOptions = {
+  baseUrl: API_URL,
+  clientId: CLIENT_ID
+};
+
+
+describe('PasswordlessAuthenticator', function () {
+
+  afterEach(function () {
+    nock.cleanAll();
+  });
+
+
+  describe('#constructor', function () {
+    it('should require an options object', function () {
+      expect(Authenticator)
+        .to.throw(ArgumentError, 'Missing authenticator options');
+
+      expect(Authenticator.bind(null, 1))
+        .to.throw(ArgumentError, 'The authenticator options must be an object');
+
+      expect(Authenticator.bind(null, validOptions))
+        .to.not.throw(ArgumentError);
+    });
+  });
+
+
+  describe('instance', function () {
+    var methods = ['signIn', 'sendEmail', 'sendSMS'];
+    var oauth = new OAuth(validOptions);
+    var authenticator = new Authenticator(validOptions, oauth);
+
+    methods.forEach(function (method) {
+      it('should have a ' + method + ' method', function () {
+        expect(authenticator[method])
+          .to.exist
+          .to.be.an.instanceOf(Function);
+      });
+    });
+  });
+
+
+  describe('#signIn', function () {
+    var path = '/oauth/ro';
+    var userData = {
+      username: 'username',
+      password: 'pwd'
+    };
+
+    beforeEach(function () {
+      var oauth = new OAuth(validOptions);
+      this.authenticator = new Authenticator(validOptions, oauth);
+      this.request = nock(API_URL)
+        .post(path)
+        .reply(200);
+    });
+
+
+    it('should require an object as first argument', function () {
+      expect(this.authenticator.signIn)
+        .to.throw(ArgumentError, 'Missing user data object');
+    });
+
+
+    it('should require a phone number', function () {
+      var auth = this.authenticator;
+      var userData = { password: 'password' };
+      var signIn = auth.signIn.bind(auth, userData);
+
+      expect(signIn)
+        .to.throw(ArgumentError, 'username field (phone number) is required');
+    });
+
+
+    it('should require a verification code', function () {
+      var auth = this.authenticator;
+      var userData = { username: 'username' };
+      var signIn = auth.signIn.bind(auth, userData);
+
+      expect(signIn)
+        .to.throw(ArgumentError, 'password field (verification code) is required');
+    });
+
+
+    it('should accept a callback', function (done) {
+      this
+        .authenticator
+        .signIn(userData, done.bind(null, null));
+    });
+
+
+    it('should return a promise when no callback is provided', function (done) {
+      this
+        .authenticator
+        .signIn(userData)
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+
+    it('should perform a POST request to ' + path, function (done) {
+      var request = this.request;
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should include the user data in the request', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          for (var property in userData) {
+            if (userData[property] !== body[property]) {
+              return false;
+            }
+          }
+
+          return true;
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should include the Auth0 client ID in the request', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.client_id === CLIENT_ID;
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should use SMS connection', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.connection === 'sms';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('shouldn\'t allow the user to specify the connection', function (done) {
+      nock.cleanAll();
+
+      var data = extend({ connection: 'TEST_CONNECTION' }, userData);
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.connection === 'sms';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(data)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should use password as grant type', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.grant_type === 'password';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should use the openid scope', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.scope === 'openid';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .signIn(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+  });
+
+
+  describe('#sendEmail', function () {
+    var path = '/passwordless/start';
+    var userData = {
+      email: 'email@domain.com',
+      send: 'link'
+    };
+
+    beforeEach(function () {
+      var oauth = new OAuth(validOptions);
+      this.authenticator = new Authenticator(validOptions, oauth);
+      this.request = nock(API_URL)
+        .post(path)
+        .reply(200);
+    });
+
+
+    it('should require an object as first argument', function () {
+      expect(this.authenticator.sendEmail)
+        .to.throw(ArgumentError, 'Missing user data object');
+    });
+
+
+    it('should require an email', function () {
+      var auth = this.authenticator;
+      var userData = {};
+      var sendEmail = auth.sendEmail.bind(auth, userData);
+
+      expect(sendEmail)
+        .to.throw(ArgumentError, 'email field is required');
+    });
+
+
+    it('should require the send field', function () {
+      var auth = this.authenticator;
+      var userData = { email: 'email@domain.com' };
+      var sendEmail = auth.sendEmail.bind(auth, userData);
+
+      expect(sendEmail)
+        .to.throw(ArgumentError, 'send field is required');
+    });
+
+
+    it('should accept a callback', function (done) {
+      this
+        .authenticator
+        .sendEmail(userData, done.bind(null, null));
+    });
+
+
+    it('should return a promise when no callback is provided', function (done) {
+      this
+        .authenticator
+        .sendEmail(userData)
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+
+    it('should perform a POST request to ' + path, function (done) {
+      var request = this.request;
+
+      this
+        .authenticator
+        .sendEmail(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should include the user data in the request', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          for (var property in userData) {
+            if (userData[property] !== body[property]) {
+              return false;
+            }
+          }
+
+          return true;
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .sendEmail(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should include the Auth0 client ID in the request', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.client_id === CLIENT_ID;
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .sendEmail(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should use the email connection', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.connection === 'email';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .sendEmail(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should use the specified send type', function (done) {
+      nock.cleanAll();
+
+      var data = extend({}, userData);
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.send === 'code';
+        })
+        .reply(200);
+
+      data.send = 'code';
+
+      this
+        .authenticator
+        .sendEmail(data)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('shouldn\'t allow the user to specify the connection', function (done) {
+      nock.cleanAll();
+
+      var data = extend({ connection: 'TEST_CONNECTION' }, userData);
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.connection === 'email';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .sendEmail(data)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+  });
+
+
+  describe('#sendSMS', function () {
+    var path = '/passwordless/start';
+    var userData = {
+      phone_number: '12345678'
+    };
+
+    beforeEach(function () {
+      var oauth = new OAuth(validOptions);
+      this.authenticator = new Authenticator(validOptions, oauth);
+      this.request = nock(API_URL)
+        .post(path)
+        .reply(200);
+    });
+
+
+    it('should require an object as first argument', function () {
+      expect(this.authenticator.sendSMS)
+        .to.throw(ArgumentError, 'Missing user data object');
+    });
+
+
+    it('should require a phone number', function () {
+      var auth = this.authenticator;
+      var userData = {};
+      var sendSMS = auth.sendSMS.bind(auth, userData);
+
+      expect(sendSMS)
+        .to.throw(ArgumentError, 'phone_number field is required');
+    });
+
+    it('should accept a callback', function (done) {
+      this
+        .authenticator
+        .sendSMS(userData, done.bind(null, null));
+    });
+
+
+    it('should return a promise when no callback is provided', function (done) {
+      this
+        .authenticator
+        .sendSMS(userData)
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+
+    it('should perform a POST request to ' + path, function (done) {
+      var request = this.request;
+
+      this
+        .authenticator
+        .sendSMS(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should include the user data in the request', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          for (var property in userData) {
+            if (userData[property] !== body[property]) {
+              return false;
+            }
+          }
+
+          return true;
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .sendSMS(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should include the Auth0 client ID in the request', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.client_id === CLIENT_ID;
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .sendSMS(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should use the sms connection', function (done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.connection === 'sms';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .sendSMS(userData)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('shouldn\'t allow the user to specify the connection', function (done) {
+      nock.cleanAll();
+
+      var data = extend({ connection: 'TEST_CONNECTION' }, userData);
+      var request = nock(API_URL)
+        .post(path, function (body) {
+          return body.connection === 'sms';
+        })
+        .reply(200);
+
+      this
+        .authenticator
+        .sendSMS(data)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+  });
+});
diff --git a/test/auth/tokens-manager.tests.js b/test/auth/tokens-manager.tests.js
new file mode 100644
index 000000000..bbae1b89c
--- /dev/null
+++ b/test/auth/tokens-manager.tests.js
@@ -0,0 +1,389 @@
+var expect = require('chai').expect;
+var nock = require('nock');
+
+var BASE_URL = 'https://tenant.auth0.com';
+
+var Promise = require('bluebird');
+var ArgumentError = require('../../src/exceptions/').ArgumentError;
+var TokensManager = require('../../src/auth/TokensManager');
+
+
+describe('TokensManager', function () {
+  var validOptions = {
+    baseUrl: BASE_URL,
+    headers: {
+      'Content-Type': 'application/json',
+      'Test-Header': 'TEST'
+    },
+    clientId: 'CLIENT_ID'
+  };
+
+  afterEach(function () {
+    nock.cleanAll();
+  });
+
+
+  describe('#constructor', function () {
+    it('should require an options object', function () {
+      expect(TokensManager)
+        .to.throw(ArgumentError, 'Missing tokens manager options');
+    });
+
+    it('should require a base URL', function () {
+      var manager = TokensManager.bind(null, {});
+
+      expect(manager)
+        .to.throw(ArgumentError, 'baseUrl field is required');
+    });
+  });
+
+
+  describe('instance', function () {
+    var methods = ['getInfo', 'getDelegationToken'];
+    var manager = new TokensManager(validOptions);
+
+    methods.forEach(function (methodName) {
+      it('should have a ' + methodName + ' method', function () {
+        expect(manager[methodName])
+          .to.exist
+          .to.be.an.instanceOf(Function);
+      });
+    });
+  });
+
+
+  describe('#getInfo', function () {
+    var manager = new TokensManager(validOptions);
+    var path = '/tokeninfo';
+
+    beforeEach(function () {
+      this.request = nock(BASE_URL)
+        .post(path)
+        .reply(200);
+    });
+
+
+    it('should require an ID token', function () {
+      var getInfo = manager.getInfo.bind(manager);
+
+      expect(getInfo)
+        .to.throw(ArgumentError, 'An ID token is required');
+    });
+
+
+    it('should throw an error when the token is invalid', function () {
+      var getInfo = manager.getInfo.bind(manager, '');
+
+      expect(getInfo)
+        .to.throw(ArgumentError, 'The ID token is not valid');
+    });
+
+
+    it('should not throw errors when the token is valid', function () {
+      var getInfo = manager.getInfo.bind(manager, 'VALID_TOKEN');
+
+      expect(getInfo)
+        .to.not.throw(ArgumentError);
+    });
+
+
+    it('should accept a callback', function (done) {
+      manager
+        .getInfo('VALID_TOKEN', done.bind(null, null));
+    });
+
+
+    it('should return a promise when no callback is provided', function () {
+      var returnValue = manager.getInfo('VALID_TOKEN');
+
+      expect(returnValue)
+        .to.be.an.instanceOf(Promise);
+    });
+
+
+    it('should not return a promise when a callback is provided', function () {
+      var returnValue = manager.getInfo('VALID_TOKEN', function () {});
+
+      expect(returnValue)
+        .to.be.undefined;
+    });
+
+
+    it('should perform a POST request to ' + path, function (done) {
+      var request = this.request;
+
+      manager
+        .getInfo('VALID_TOKEN')
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+
+
+    it('should include the headers specified in the instance options', function (done) {
+      nock.cleanAll();
+
+      var request = nock(BASE_URL)
+        .post(path)
+        .matchHeader('Content-Type', validOptions.headers['Content-Type'])
+        .reply(200);
+
+      manager
+        .getInfo('VALID_TOKEN')
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+
+
+    it('should send the ID token in the body of the request', function (done) {
+      nock.cleanAll();
+
+      var request = nock(BASE_URL)
+        .post(path, function (body) {
+          return body.id_token === 'VALID_TOKEN';
+        })
+        .reply(200);
+
+      manager
+        .getInfo('VALID_TOKEN')
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+  });
+
+
+  describe('#getDelegationToken', function () {
+    var path = '/delegation';
+    var manager = new TokensManager(validOptions);
+
+    beforeEach(function () {
+      this.request = nock(BASE_URL)
+        .post(path)
+        .reply(200);
+    });
+
+
+    it('should require a data object', function () {
+      var getDelegationToken = manager.getDelegationToken.bind(manager);
+
+      expect(getDelegationToken)
+        .to.throw(ArgumentError, 'Missing token data object');
+    });
+
+
+    it('should require an ID token' ,function () {
+      var data = {};
+      var getDelegationToken = manager.getDelegationToken.bind(manager, data);
+
+      expect(getDelegationToken)
+        .to.throw(ArgumentError, 'id_token field is required');
+    });
+
+
+    it('should require a target client', function () {
+      var data = { id_token: 'TEST_ID_TOKEN' };
+      var getDelegationToken = manager.getDelegationToken.bind(manager, data);
+
+      expect(getDelegationToken)
+        .to.throw(ArgumentError, 'target field is required');
+    });
+
+
+    it('should require an API type', function () {
+      var data = {
+        id_token: 'TEST_ID_TOKEN',
+        target: 'TEST_TARGET'
+      };
+      var getDelegationToken = manager.getDelegationToken.bind(manager, data);
+
+      expect(getDelegationToken)
+        .to.throw(ArgumentError, 'api_type field is required');
+    });
+
+
+    it('should require an grant type', function () {
+      var data = {
+        id_token: 'TEST_ID_TOKEN',
+        target: 'TEST_TARGET',
+        api_type: 'aws'
+      };
+      var getDelegationToken = manager.getDelegationToken.bind(manager, data);
+
+      expect(getDelegationToken)
+        .to.throw(ArgumentError, 'grant_type field is required');
+    });
+
+
+    it('should accept a callback', function (done) {
+      var data = {
+        id_token: 'TEST_ID_TOKEN',
+        target: 'TEST_TARGET',
+        api_type: 'aws',
+        grant_type: 'SAMPLE_GRANT_TYPE'
+      };
+
+      manager.getDelegationToken(data, done.bind(null, null));
+    });
+
+
+    it('should return a promise when no callback is given', function () {
+      var data = {
+        id_token: 'TEST_ID_TOKEN',
+        target: 'TEST_TARGET',
+        api_type: 'aws',
+        grant_type: 'SAMPLE_GRANT_TYPE'
+      };
+      var returnValue = manager.getDelegationToken(data);
+
+      expect(returnValue)
+        .to.be.an.instanceOf(Promise);
+    });
+
+
+    it('should not return a promise when a callback is given', function () {
+      var data = {
+        id_token: 'TEST_ID_TOKEN',
+        target: 'TEST_TARGET',
+        api_type: 'aws',
+        grant_type: 'SAMPLE_GRANT_TYPE'
+      };
+      var returnValue = manager.getDelegationToken(
+        data,
+        function () {}
+      );
+
+      expect(returnValue)
+        .to.equal(undefined);
+    });
+
+
+    it('should perform a POST request to ' + path, function () {
+
+    });
+
+    it('should include the data in the body of the request', function (done) {
+      nock.cleanAll();
+
+      var data = {
+        id_token: 'TEST_ID_TOKEN',
+        target: 'TEST_TARGET',
+        api_type: 'aws',
+        grant_type: 'SAMPLE_GRANT_TYPE'
+      };
+
+      var request = nock(BASE_URL)
+        .post(path, function (body) {
+          for (var property in data) {
+            if (body[property] !== data[property]) {
+              return false;
+            }
+          }
+
+          return true;
+        })
+        .reply();
+
+      manager
+        .getDelegationToken(data)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+
+
+    it('should use the instance client ID if none specified', function (done) {
+      nock.cleanAll();
+
+      var data = {
+        id_token: 'TEST_ID_TOKEN',
+        target: 'TEST_TARGET',
+        api_type: 'aws',
+        grant_type: 'SAMPLE_GRANT_TYPE'
+      };
+
+      var request = nock(BASE_URL)
+        .post(path, function (body) {
+          return body.client_id === validOptions.clientId;
+        })
+        .reply();
+
+      manager
+        .getDelegationToken(data)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+
+
+    it('should let the user override the default client ID', function (done) {
+      nock.cleanAll();
+
+      var data = {
+        id_token: 'TEST_ID_TOKEN',
+        target: 'TEST_TARGET',
+        api_type: 'aws',
+        grant_type: 'SAMPLE_GRANT_TYPE',
+        client_id: 'OVERRIDEN_CLIENT_ID'
+      };
+
+      var request = nock(BASE_URL)
+        .post(path, function (body) {
+          return body.client_id === data.client_id;
+        })
+        .reply();
+
+      manager
+        .getDelegationToken(data)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+
+
+    it('should include the headers specified in the instance options', function (done) {
+      nock.cleanAll();
+
+      var data = {
+        id_token: 'TEST_ID_TOKEN',
+        target: 'TEST_TARGET',
+        api_type: 'aws',
+        grant_type: 'SAMPLE_GRANT_TYPE'
+      };
+
+      var request = nock(BASE_URL)
+        .post(path)
+        .matchHeader('Test-Header', validOptions.headers['Test-Header'])
+        .reply(200);
+
+      manager
+        .getDelegationToken(data)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+  });
+
+});
diff --git a/test/auth/user-profile.tests.js b/test/auth/user-profile.tests.js
new file mode 100644
index 000000000..88bce4d58
--- /dev/null
+++ b/test/auth/user-profile.tests.js
@@ -0,0 +1,22 @@
+var expect = require('chai').expect;
+var extend = require('util')._extend;
+var nock = require('nock');
+
+// Constants.
+var SRC_DIR = '../../src';
+var DOMAIN = 'tenant.auth0.com';
+var API_URL = 'https://' + DOMAIN;
+var CLIENT_ID = 'TEST_CLIENT_ID';
+
+var ArgumentError = require(SRC_DIR + '/exceptions').ArgumentError;
+var Authenticator = require(SRC_DIR + '/auth/OAuthAuthenticator');
+
+var validOptions = {
+  domain: DOMAIN,
+  clientId: CLIENT_ID
+};
+
+
+describe('UserProfile', function () {
+
+});
diff --git a/test/auth/users-manager.tests.js b/test/auth/users-manager.tests.js
new file mode 100644
index 000000000..4eccaa9b4
--- /dev/null
+++ b/test/auth/users-manager.tests.js
@@ -0,0 +1,379 @@
+var expect = require('chai').expect;
+var nock = require('nock');
+var Promise = require('bluebird');
+
+var BASE_URL = 'https://tenant.auth0.com';
+var CLIENT_ID = 'TEST_CLIENT_ID';
+
+var ArgumentError = require('../../src/exceptions').ArgumentError;
+var UsersManager = require('../../src/auth/UsersManager');
+
+
+describe('UsersManager', function () {
+  var options = {
+    baseUrl: BASE_URL,
+    clientId: CLIENT_ID,
+    headers: {
+      'Content-Type': 'application/json'
+    }
+  };
+
+  afterEach(function () {
+    nock.cleanAll();
+  });
+
+
+  describe('#constructor', function () {
+    it ('should require an options object', function () {
+      expect(UsersManager)
+        .to.throw(ArgumentError, 'Missing users manager options');
+    });
+
+
+    it ('should require a base URL', function () {
+      expect(UsersManager.bind(null, {}))
+        .to.throw(ArgumentError, 'baseUrl field is required');
+    });
+  });
+
+
+  describe('instance', function () {
+    var manager = new UsersManager(options);
+    var methods = ['getInfo', 'impersonate'];
+
+    methods.forEach(function (methodName) {
+      it('should have a ' + methodName + ' method', function () {
+        expect(manager[methodName])
+          .to.exist
+          .to.be.an.instanceOf(Function);
+      });
+    });
+  });
+
+
+  describe('#getInfo', function () {
+    var manager = new UsersManager(options);
+    var path = '/userinfo';
+
+    beforeEach(function () {
+      this.request = nock(BASE_URL)
+        .get(path)
+        .reply(200);
+    });
+
+
+    it('should require an access token', function () {
+      var getInfo = manager.getInfo.bind(manager);
+
+      expect(getInfo)
+        .to.throw(ArgumentError, 'An access token is required');
+    });
+
+
+    it('should throw an error when the token is invalid', function () {
+      var getInfo = manager.getInfo.bind(manager, '');
+
+      expect(getInfo)
+        .to.throw(ArgumentError, 'Invalid access token');
+    });
+
+
+    it('should not throw errors when the token is valid', function () {
+      var getInfo = manager.getInfo.bind(manager, 'VALID_TOKEN');
+
+      expect(getInfo)
+        .to.not.throw(ArgumentError);
+    });
+
+
+    it('should accept a callback', function (done) {
+      manager
+        .getInfo('ACCESS_TOKEN', done.bind(null, null))
+    });
+
+
+    it('should not return a promise when a callback is provided', function () {
+      var returnValue = manager.getInfo('ACCESS_TOKEN', function () {});
+
+      expect(returnValue)
+        .to.equal(undefined);
+    });
+
+
+    it('should return a promise when no callback is provided', function () {
+      var returnValue = manager.getInfo('ACCESS_TOKEN');
+
+      expect(returnValue)
+        .to.be.an.instanceOf(Promise);
+    });
+
+
+    it('should perform a GET request to ' + path, function (done) {
+      var request = this.request;
+
+      manager
+        .getInfo('ACCESS_TOKEN')
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should include the headers specified in the UsersManager options', function (done) {
+      nock.cleanAll();
+
+      var request = nock(BASE_URL)
+        .get('/userinfo')
+        .matchHeader('Content-Type', options.headers['Content-Type'])
+        .reply(200);
+
+      manager
+        .getInfo('ACCESS_TOKEN')
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should send the access token in the Authorization header', function (done) {
+      nock.cleanAll();
+
+      var request = nock(BASE_URL)
+        .get('/userinfo')
+        .matchHeader('Authorization', 'Bearer ACCESS_TOKEN')
+        .reply(200);
+
+      manager
+        .getInfo('ACCESS_TOKEN')
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+  });
+
+
+  describe('#impersonate', function () {
+    var USER_ID = encodeURIComponent('github|12345');
+    var manager = new UsersManager(options);
+    var path = '/users/' + USER_ID + '/impersonate';
+
+    beforeEach(function () {
+      this.request = nock(BASE_URL)
+        .post(path)
+        .reply(200);
+    });
+
+
+    it('should require a user ID', function () {
+      var impersonate = manager.impersonate.bind(manager);
+
+      expect(impersonate)
+        .to.throw(ArgumentError, 'You must specify a user ID');
+    });
+
+
+    it('should throw an error when the user ID is not valid', function () {
+      var impersonate = manager.impersonate.bind(manager, '');
+
+      expect(impersonate)
+        .to.throw(ArgumentError, 'The user ID is not valid');
+    });
+
+
+    it('should require the impersonation settings object', function () {
+      var impersonate = manager.impersonate.bind(manager, USER_ID);
+
+      expect(impersonate)
+        .to.throw(ArgumentError, 'Missing impersonation settings object');
+    });
+
+
+    it('should require an impersonator ID', function () {
+      var impersonate = manager.impersonate.bind(manager, USER_ID, {});
+
+      expect(impersonate)
+        .to.throw(ArgumentError, 'impersonator_id field is required');
+    });
+
+
+    it('should require a protocol', function () {
+      var settings = {
+        impersonator_id: 'auth0|12345'
+      };
+      var impersonate = manager.impersonate.bind(manager, USER_ID, settings);
+
+      expect(impersonate)
+        .to.throw(ArgumentError, 'protocol field is required');
+    });
+
+
+    it('should accept a callback', function (done) {
+      var settings = {
+        impersonator_id: 'auth0|12345',
+        protocol: 'oauth2'
+      };
+
+      manager.impersonate(USER_ID, settings, done.bind(null, null));
+    });
+
+
+    it('should not return a promise when a callback is provided', function () {
+      var settings = {
+        impersonator_id: 'auth0|12345',
+        protocol: 'oauth2'
+      };
+      var returnValue = manager.impersonate(
+        USER_ID,
+        settings,
+        function () {}
+      );
+
+      expect(returnValue)
+        .to.equal(undefined);
+    });
+
+
+    it('should return a promise when no callback is provided', function () {
+      var settings = {
+        impersonator_id: 'auth0|12345',
+        protocol: 'oauth2'
+      };
+      var returnValue = manager.impersonate(USER_ID, settings);
+
+      expect(returnValue)
+        .to.be.an.instanceOf(Promise);
+    });
+
+
+    it('should perform a POST request to ' + path, function (done) {
+      var request = this.request;
+      var settings = {
+        impersonator_id: 'auth0|12345',
+        protocol: 'oauth2'
+      };
+
+      manager
+        .impersonate(USER_ID, settings)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        })
+        .catch(done);
+    });
+
+
+    it('should use the default client ID', function (done) {
+      nock.cleanAll();
+
+      var settings = {
+        impersonator_id: 'auth0|12345',
+        protocol: 'oauth2'
+      };
+      var request = nock(BASE_URL)
+        .post(path, function (body) {
+          return body.client_id === CLIENT_ID
+        })
+        .reply(200);
+
+      manager
+        .impersonate(USER_ID, settings)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+
+
+    it('should allow the user to override the client ID', function (done) {
+      nock.cleanAll();
+
+      var settings = {
+        impersonator_id: 'auth0|12345',
+        protocol: 'oauth2',
+        client_id: 'OVERRIDEN_CLIENT_ID'
+      };
+      var request = nock(BASE_URL)
+        .post(path, function (body) {
+          return body.client_id === 'OVERRIDEN_CLIENT_ID'
+        })
+        .reply(200);
+
+      manager
+        .impersonate(USER_ID, settings)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+
+
+    it('should use the default headers', function (done) {
+      nock.cleanAll();
+
+      var settings = {
+        impersonator_id: 'auth0|12345',
+        protocol: 'oauth2',
+      };
+      var request = nock(BASE_URL)
+        .post(path)
+        .matchHeader('Content-Type', options.headers['Content-Type'])
+        .reply(200);
+
+      manager
+        .impersonate(USER_ID, settings)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+    });
+
+
+    it('should allow the user to add additional parameters', function (done) {
+      nock.cleanAll();
+
+      var settings = {
+        impersonator_id: 'auth0|12345',
+        protocol: 'oauth2',
+        additionalParameters: {
+          response_type: 'code'
+        }
+      };
+      var request = nock(BASE_URL)
+        .post(path, function (body) {
+          return body.additionalParameters.response_type === 'code';
+        })
+        .reply(200);
+
+      manager
+        .impersonate(USER_ID, settings)
+        .then(function () {
+          expect(request.isDone())
+            .to.be.true;
+
+          done();
+        });
+
+    });
+  });
+
+});
diff --git a/test/auth0.tests.js b/test/auth0.tests.js
index b030bdf1a..bfd8fbb30 100644
--- a/test/auth0.tests.js
+++ b/test/auth0.tests.js
@@ -1,167 +1,21 @@
 var expect = require('chai').expect;
 
 var auth0 = require('../src');
-var Auth0 = require('../src/Auth0');
-var ArgumentError = require('../src/exceptions').ArgumentError;
-var UsersManager = require('../src/UsersManager');
-var BlacklistedTokensManager = require('../src/BlacklistedTokensManager');
-var ClientsManager = require('../src/ClientsManager');
-var ConnectionsManager = require('../src/ConnectionsManager');
-var DeviceCredentialsManager = require('../src/DeviceCredentialsManager');
-var EmailProviderManager = require('../src/EmailProviderManager');
-var JobsManager = require('../src/JobsManager');
-var RulesManager = require('../src/RulesManager');
-var StatsManager = require('../src/StatsManager');
-var TenantManager = require('../src/TenantManager');
+var AuthenticationClient = require('../src/auth');
+var ManagementClient = require('../src/management');
 
 
 describe('Auth0 module', function () {
 
-    it('should raise an error when no options object is provided', function () {
-      expect(auth0)
-        .to.throw(ArgumentError, 'Auth0 SDK options must be an object');
+    it('should expose the AuthenticationClient', function () {
+      expect(auth0.AuthenticationClient)
+        .to.equal(AuthenticationClient);
     });
 
 
-    it('should raise an error when the token is not valid', function () {
-      var client = auth0.bind(null, { token: '', domain: 'tenant.auth.com' });
-
-      expect(client)
-        .to.throw(ArgumentError, 'An access token must be provided');
-    });
-
-
-    it('should raise an error when the domain is not valid', function () {
-      var client = auth0.bind(null, { token: 'token', domain: '' });
-
-      expect(client)
-        .to.throw(ArgumentError, 'Must provide a domain');
-    });
-
-
-    it('should return an instance of Auth0 when the options are valid', function () {
-      var options = { token: 'token', domain: 'tenant.auth0.com' };
-      var client = auth0(options);
-
-      expect(client)
-        .to.be.an('Object')
-        .to.be.an.instanceOf(Auth0);
-    });
-
-
-    describe('instance properties', function () {
-      var manager;
-      var managers = {
-        'UsersManager': {
-          property: 'users',
-          cls: UsersManager
-        },
-        'BlacklistedTokensManager': {
-          property: 'blacklistedTokens',
-          cls: BlacklistedTokensManager
-        },
-        'ClientsManager': {
-          property: 'clients',
-          cls: ClientsManager
-        },
-        'ConnectionsManager': {
-          property: 'connections',
-          cls: ConnectionsManager
-        },
-        'DeviceCredentialsManager': {
-          property: 'deviceCredentials',
-          cls: DeviceCredentialsManager
-        },
-        'EmailProviderManager': {
-          property: 'emailProvider',
-          cls: EmailProviderManager
-        },
-        'JobsManager': {
-          property: 'jobs',
-          cls: JobsManager
-        },
-        'RulesManager': {
-          property: 'rules',
-          cls: RulesManager
-        },
-        'StatsManager': {
-          property: 'stats',
-          cls: StatsManager
-        },
-        'TenantManager': {
-          property: 'tenant',
-          cls: TenantManager
-        }
-      };
-
-      before(function () {
-        this.client = auth0({ token: 'token', domain: 'auth0.com' });
-      });
-
-      // Tests common to all managers.
-      for (var name in managers) {
-        manager = managers[name];
-
-        it('should expose an instance of ' + name, function () {
-          expect(this.client[manager.property])
-            .to.exist
-            .to.be.an.instanceOf(manager.cls);
-        });
-      }
+    it('should expose the ManagementClient', function () {
+      expect(auth0.ManagementClient)
+        .to.equal(ManagementClient);
     });
 
-
-    describe('instance methods', function () {
-      var method;
-      var methods = [
-        'getConnections',
-        'createConnection',
-        'getConnection',
-        'deleteConnection',
-        'updateConnection',
-        'getClients',
-        'getClient',
-        'createClient',
-        'updateClient',
-        'deleteClient',
-        'createDevicePublicKey',
-        'getDeviceCredentials',
-        'deleteDeviceCredential',
-        'getRules',
-        'createRule',
-        'getRule',
-        'deleteRule',
-        'updateRule',
-        'getUsers',
-        'getUser',
-        'deleteAllUsers',
-        'deleteUsers',
-        'createUser',
-        'updateUser',
-        'getBlacklistedTokens',
-        'blacklistToken',
-        'getEmailProvider',
-        'configureEmailProvider',
-        'deleteEmailProvider',
-        'updateEmailProvider',
-        'getActiveUsersCount',
-        'getDailyStats',
-        'getTenantSettings',
-        'updateTenantSettings'
-      ];
-
-      before(function () {
-        this.client = auth0({ token: 'token', domain: 'auth0.com' });
-      });
-
-      for (var i = 0, l = methods.length; i < l; i++) {
-        method = methods[i];
-
-        it('should have a ' + method + ' method', function () {
-          expect(this.client[method])
-            .to.exist
-            .to.be.an.instanceOf(Function);
-        });
-      }
-    });
 });
diff --git a/test/blacklisted-tokens.tests.js b/test/management/blacklisted-tokens.tests.js
similarity index 96%
rename from test/blacklisted-tokens.tests.js
rename to test/management/blacklisted-tokens.tests.js
index 9770b3f37..bdae9ee92 100644
--- a/test/blacklisted-tokens.tests.js
+++ b/test/management/blacklisted-tokens.tests.js
@@ -1,11 +1,12 @@
 var expect = require('chai').expect;
 var nock = require('nock');
 
-var BlacklistedTokensManager = require('../src/BlacklistedTokensManager');
-var ArgumentError = require('../src/exceptions').ArgumentError;
-
+var SRC_DIR = '../../src';
 var API_URL = 'https://tenant.auth0.com';
 
+var BlacklistedTokensManager = require(SRC_DIR + '/management/BlacklistedTokensManager');
+var ArgumentError = require(SRC_DIR + '/exceptions').ArgumentError;
+
 
 describe('BlacklistedTokensManager', function () {
   before(function () {
@@ -167,7 +168,7 @@ describe('BlacklistedTokensManager', function () {
 
       this
         .blacklistedTokens
-        .getAll({ includeFields: true, fields: 'test' })
+        .getAll({ include_fields: true, fields: 'test' })
         .then(function () {
           expect(request.isDone()).to.be.true;
           done();
diff --git a/test/client.tests.js b/test/management/client.tests.js
similarity index 97%
rename from test/client.tests.js
rename to test/management/client.tests.js
index b4f5d9c2a..6e5cd60b7 100644
--- a/test/client.tests.js
+++ b/test/management/client.tests.js
@@ -1,11 +1,13 @@
 var expect = require('chai').expect;
 var nock = require('nock');
 var Promise = require('bluebird');
-var ClientsManager = require('../src/ClientsManager');
 
-var ArgumentError = require('../src/exceptions').ArgumentError;
+var SRC_DIR = '../../src';
 var API_URL = 'https://tenant.auth0.com';
 
+var ClientsManager = require(SRC_DIR + '/management/ClientsManager');
+var ArgumentError = require(SRC_DIR + '/exceptions').ArgumentError;
+
 
 describe('ClientsManager', function () {
 
@@ -171,7 +173,7 @@ describe('ClientsManager', function () {
 
       this
         .clients
-        .getAll({ includeFields: true, fields: 'test' })
+        .getAll({ include_fields: true, fields: 'test' })
         .then(function () {
           expect(request.isDone()).to.be.true;
           done();
diff --git a/test/connections.tests.js b/test/management/connections.tests.js
similarity index 97%
rename from test/connections.tests.js
rename to test/management/connections.tests.js
index 0696f2da0..4d3a2c4fe 100644
--- a/test/connections.tests.js
+++ b/test/management/connections.tests.js
@@ -1,11 +1,12 @@
 var expect = require('chai').expect;
 var nock = require('nock');
 
-var ConnectionsManager = require('../src/ConnectionsManager');
-var ArgumentError = require('../src/exceptions').ArgumentError;
-
+var SRC_DIR = '../../src';
 var API_URL = 'https://tenant.auth0.com';
 
+var ConnectionsManager = require(SRC_DIR + '/management/ConnectionsManager');
+var ArgumentError = require(SRC_DIR + '/exceptions').ArgumentError;
+
 
 describe('ConnectionsManager', function () {
   before(function () {
@@ -172,7 +173,7 @@ describe('ConnectionsManager', function () {
 
       this
         .connections
-        .getAll({ includeFields: true, fields: 'test' })
+        .getAll({ include_fields: true, fields: 'test' })
         .then(function () {
           expect(request.isDone())
             .to.be.true;
@@ -301,7 +302,7 @@ describe('ConnectionsManager', function () {
 
       this
         .connections
-        .getAll({ includeFields: true, fields: 'test' })
+        .getAll({ include_fields: true, fields: 'test' })
         .then(function () {
           expect(request.isDone())
             .to.be.true;
diff --git a/test/device-credentials.tests.js b/test/management/device-credentials.tests.js
similarity index 97%
rename from test/device-credentials.tests.js
rename to test/management/device-credentials.tests.js
index 92a5690ba..bb13293af 100644
--- a/test/device-credentials.tests.js
+++ b/test/management/device-credentials.tests.js
@@ -1,11 +1,12 @@
 var expect = require('chai').expect;
 var nock = require('nock');
 
-var DeviceCredentialsManager = require('../src/DeviceCredentialsManager');
-var ArgumentError = require('../src/exceptions').ArgumentError;
-
+var SRC_DIR = '../../src';
 var API_URL = 'https://tenant.auth0.com';
 
+var DeviceCredentialsManager = require(SRC_DIR + '/management/DeviceCredentialsManager');
+var ArgumentError = require(SRC_DIR + '/exceptions').ArgumentError;
+
 
 describe('DeviceCredentialsManager', function () {
   before(function () {
@@ -158,7 +159,7 @@ describe('DeviceCredentialsManager', function () {
       nock.cleanAll();
 
       var params = {
-        includeFields: true,
+        include_fields: true,
         fields: 'test'
       };
       var request = nock(API_URL)
diff --git a/test/email-provider.tests.js b/test/management/email-provider.tests.js
similarity index 98%
rename from test/email-provider.tests.js
rename to test/management/email-provider.tests.js
index 2071d99e0..f9bec9d3b 100644
--- a/test/email-provider.tests.js
+++ b/test/management/email-provider.tests.js
@@ -1,11 +1,12 @@
 var expect = require('chai').expect;
 var nock = require('nock');
 
-var EmailProviderManager = require('../src/EmailProviderManager');
-var ArgumentError = require('../src/exceptions').ArgumentError;
-
+var SRC_DIR = '../../src';
 var API_URL = 'https://tenant.auth0.com';
 
+var EmailProviderManager = require(SRC_DIR + '/management/EmailProviderManager');
+var ArgumentError = require(SRC_DIR + '/exceptions').ArgumentError;
+
 
 describe('EmailProviderManager', function () {
   before(function () {
diff --git a/test/jobs.tests.js b/test/management/jobs.tests.js
similarity index 96%
rename from test/jobs.tests.js
rename to test/management/jobs.tests.js
index 6b8b12912..488edb7f6 100644
--- a/test/jobs.tests.js
+++ b/test/management/jobs.tests.js
@@ -1,13 +1,14 @@
 var path = require('path');
 var expect = require('chai').expect;
 var nock = require('nock');
-var extractParts = require('./utils').extractParts;
-
-var JobsManager = require('../src/JobsManager');
-var ArgumentError = require('../src/exceptions').ArgumentError;
+var extractParts = require('../utils').extractParts;
 
+var SRC_DIR = '../../src';
 var API_URL = 'https://tenant.auth0.com';
 
+var JobsManager = require(SRC_DIR + '/management/JobsManager');
+var ArgumentError = require(SRC_DIR + '/exceptions').ArgumentError;
+
 
 describe('JobsManager', function () {
   before(function () {
@@ -172,7 +173,7 @@ describe('JobsManager', function () {
 
       this
         .jobs
-        .get({ id: this.id, includeFields: true, fields: 'test' })
+        .get({ id: this.id, include_fields: true, fields: 'test' })
         .then(function () {
           expect(request.isDone()).to.be.true;
           done();
@@ -183,7 +184,7 @@ describe('JobsManager', function () {
 
   describe('#importUsers', function () {
     var data = {
-      users: path.join(__dirname, './data/users.json'),
+      users: path.join(__dirname, '../data/users.json'),
       connection_id: 'con_test'
     };
 
diff --git a/test/management/management-client.tests.js b/test/management/management-client.tests.js
new file mode 100644
index 000000000..819c0231e
--- /dev/null
+++ b/test/management/management-client.tests.js
@@ -0,0 +1,158 @@
+var expect = require('chai').expect;
+
+var ManagementClient = require('../../src/management');
+
+var ArgumentError = require('../../src/exceptions').ArgumentError;
+var UsersManager = require('../../src/management/UsersManager');
+var BlacklistedTokensManager = require('../../src/management/BlacklistedTokensManager');
+var ClientsManager = require('../../src/management/ClientsManager');
+var ConnectionsManager = require('../../src/management/ConnectionsManager');
+var DeviceCredentialsManager = require('../../src/management/DeviceCredentialsManager');
+var EmailProviderManager = require('../../src/management/EmailProviderManager');
+var JobsManager = require('../../src/management/JobsManager');
+var RulesManager = require('../../src/management/RulesManager');
+var StatsManager = require('../../src/management/StatsManager');
+var TenantManager = require('../../src/management/TenantManager');
+
+
+describe('ManagementClient', function () {
+
+    it('should raise an error when no options object is provided', function () {
+      expect(ManagementClient)
+        .to.throw(ArgumentError, 'Management API SDK options must be an object');
+    });
+
+
+    it('should raise an error when the token is not valid', function () {
+      var options = { token: '', domain: 'tenant.auth.com' };
+      var client = ManagementClient.bind(null, options);
+
+      expect(client)
+        .to.throw(ArgumentError, 'An access token must be provided');
+    });
+
+
+    it('should raise an error when the domain is not valid', function () {
+      var client = ManagementClient.bind(null, { token: 'token', domain: '' });
+
+      expect(client)
+        .to.throw(ArgumentError, 'Must provide a domain');
+    });
+
+
+    describe('instance properties', function () {
+      var manager;
+      var managers = {
+        'UsersManager': {
+          property: 'users',
+          cls: UsersManager
+        },
+        'BlacklistedTokensManager': {
+          property: 'blacklistedTokens',
+          cls: BlacklistedTokensManager
+        },
+        'ClientsManager': {
+          property: 'clients',
+          cls: ClientsManager
+        },
+        'ConnectionsManager': {
+          property: 'connections',
+          cls: ConnectionsManager
+        },
+        'DeviceCredentialsManager': {
+          property: 'deviceCredentials',
+          cls: DeviceCredentialsManager
+        },
+        'EmailProviderManager': {
+          property: 'emailProvider',
+          cls: EmailProviderManager
+        },
+        'JobsManager': {
+          property: 'jobs',
+          cls: JobsManager
+        },
+        'RulesManager': {
+          property: 'rules',
+          cls: RulesManager
+        },
+        'StatsManager': {
+          property: 'stats',
+          cls: StatsManager
+        },
+        'TenantManager': {
+          property: 'tenant',
+          cls: TenantManager
+        }
+      };
+
+      before(function () {
+        this.client = new ManagementClient({ token: 'token', domain: 'tenant.auth0.com' });
+      });
+
+      // Tests common to all managers.
+      for (var name in managers) {
+        manager = managers[name];
+
+        it('should expose an instance of ' + name, function () {
+          expect(this.client[manager.property])
+            .to.exist
+            .to.be.an.instanceOf(manager.cls);
+        });
+      }
+    });
+
+
+    describe('instance methods', function () {
+      var method;
+      var methods = [
+        'getConnections',
+        'createConnection',
+        'getConnection',
+        'deleteConnection',
+        'updateConnection',
+        'getClients',
+        'getClient',
+        'createClient',
+        'updateClient',
+        'deleteClient',
+        'createDevicePublicKey',
+        'getDeviceCredentials',
+        'deleteDeviceCredential',
+        'getRules',
+        'createRule',
+        'getRule',
+        'deleteRule',
+        'updateRule',
+        'getUsers',
+        'getUser',
+        'deleteAllUsers',
+        'deleteUsers',
+        'createUser',
+        'updateUser',
+        'getBlacklistedTokens',
+        'blacklistToken',
+        'getEmailProvider',
+        'configureEmailProvider',
+        'deleteEmailProvider',
+        'updateEmailProvider',
+        'getActiveUsersCount',
+        'getDailyStats',
+        'getTenantSettings',
+        'updateTenantSettings'
+      ];
+
+      before(function () {
+        this.client = new ManagementClient({ token: 'token', domain: 'auth0.com' });
+      });
+
+      for (var i = 0, l = methods.length; i < l; i++) {
+        method = methods[i];
+
+        it('should have a ' + method + ' method', function () {
+          expect(this.client[method])
+            .to.exist
+            .to.be.an.instanceOf(Function);
+        });
+      }
+    });
+});
diff --git a/test/rules.tests.js b/test/management/rules.tests.js
similarity index 98%
rename from test/rules.tests.js
rename to test/management/rules.tests.js
index 2db0a9814..a64f1c999 100644
--- a/test/rules.tests.js
+++ b/test/management/rules.tests.js
@@ -1,11 +1,12 @@
 var expect = require('chai').expect;
 var nock = require('nock');
 
-var RulesManager = require('../src/RulesManager');
-var ArgumentError = require('../src/exceptions').ArgumentError;
-
+var SRC_DIR = '../../src';
 var API_URL = 'https://tenant.auth0.com';
 
+var RulesManager = require(SRC_DIR + '/management/RulesManager');
+var ArgumentError = require(SRC_DIR + '/exceptions').ArgumentError;
+
 
 describe('RulesManager', function () {
   before(function () {
@@ -158,7 +159,7 @@ describe('RulesManager', function () {
       nock.cleanAll();
 
       var params = {
-        includeFields: true,
+        include_fields: true,
         fields: 'test'
       };
       var request = nock(API_URL)
diff --git a/test/stats.tests.js b/test/management/stats.tests.js
similarity index 96%
rename from test/stats.tests.js
rename to test/management/stats.tests.js
index c0848b103..0e7d0e049 100644
--- a/test/stats.tests.js
+++ b/test/management/stats.tests.js
@@ -1,11 +1,12 @@
 var expect = require('chai').expect;
 var nock = require('nock');
 
-var StatsManager = require('../src/StatsManager');
-var ArgumentError = require('../src/exceptions').ArgumentError;
-
+var SRC_DIR = '../../src';
 var API_URL = 'https://tenant.auth0.com';
 
+var StatsManager = require(SRC_DIR + '/management/StatsManager');
+var ArgumentError = require(SRC_DIR + '/exceptions').ArgumentError;
+
 
 describe('StatsManager', function () {
   before(function () {
@@ -167,7 +168,7 @@ describe('StatsManager', function () {
 
       this
         .stats
-        .getDaily({ includeFields: true, fields: 'test' })
+        .getDaily({ include_fields: true, fields: 'test' })
         .then(function () {
           expect(request.isDone()).to.be.true;
           done();
diff --git a/test/tenant.tests.js b/test/management/tenant.tests.js
similarity index 96%
rename from test/tenant.tests.js
rename to test/management/tenant.tests.js
index fd366c8db..1b71e254c 100644
--- a/test/tenant.tests.js
+++ b/test/management/tenant.tests.js
@@ -1,11 +1,12 @@
 var expect = require('chai').expect;
 var nock = require('nock');
 
-var TenantManager = require('../src/TenantManager');
-var ArgumentError = require('../src/exceptions').ArgumentError;
-
+var SRC_DIR = '../../src';
 var API_URL = 'https://tenants.auth0.com';
 
+var TenantManager = require(SRC_DIR + '/management/TenantManager');
+var ArgumentError = require(SRC_DIR + '/exceptions').ArgumentError;
+
 
 describe('TenantManager', function () {
   before(function () {
@@ -167,7 +168,7 @@ describe('TenantManager', function () {
 
       this
         .tenant
-        .getSettings({ includeFields: true, fields: 'test' })
+        .getSettings({ include_fields: true, fields: 'test' })
         .then(function () {
           expect(request.isDone()).to.be.true;
           done();
diff --git a/test/tickets.tests.js b/test/management/tickets.tests.js
similarity index 96%
rename from test/tickets.tests.js
rename to test/management/tickets.tests.js
index 0936ce7b7..7d682d8c0 100644
--- a/test/tickets.tests.js
+++ b/test/management/tickets.tests.js
@@ -1,11 +1,12 @@
 var expect = require('chai').expect;
 var nock = require('nock');
 
-var TicketsManager = require('../src/TicketsManager');
-var ArgumentError = require('../src/exceptions').ArgumentError;
-
+var SRC_DIR = '../../src';
 var API_URL = 'https://tenants.auth0.com';
 
+var TicketsManager = require(SRC_DIR + '/management/TicketsManager');
+var ArgumentError = require(SRC_DIR + '/exceptions').ArgumentError;
+
 
 describe('TicketsManager', function () {
   before(function () {
@@ -146,7 +147,7 @@ describe('TicketsManager', function () {
 
       this
         .tickets
-        .verifyEmail({ includeFields: true, fields: 'test' })
+        .verifyEmail({ include_fields: true, fields: 'test' })
         .then(function () {
           expect(request.isDone())
             .to.be.true;
diff --git a/test/users.tests.js b/test/management/users.tests.js
similarity index 98%
rename from test/users.tests.js
rename to test/management/users.tests.js
index 8ca382c2e..d71b98f45 100644
--- a/test/users.tests.js
+++ b/test/management/users.tests.js
@@ -1,10 +1,11 @@
 var expect = require('chai').expect;
 var nock = require('nock');
 
-var UsersManager = require('../src/UsersManager');
-var ArgumentError = require('../src/exceptions').ArgumentError;
+var SRC_DIR = '../../src';
+var API_URL = 'https://tenants.auth0.com';
 
-var API_URL = 'https://tenant.auth0.com/api/v2';
+var UsersManager = require(SRC_DIR + '/management/UsersManager');
+var ArgumentError = require(SRC_DIR + '/exceptions').ArgumentError;
 
 
 describe('UsersManager', function () {
@@ -174,7 +175,7 @@ describe('UsersManager', function () {
       nock.cleanAll();
 
       var params = {
-        includeFields: true,
+        include_fields: true,
         fields: 'test'
       };
       var request = nock(API_URL)
@@ -473,7 +474,7 @@ describe('UsersManager', function () {
 
 
   describe('#delete', function () {
-    var id = 5;
+    var id = 'USER_5';
 
     beforeEach(function () {
       this.request = nock(API_URL)
diff --git a/test/utils/ensureMethod.js b/test/utils/ensureMethod.js
new file mode 100644
index 000000000..efcc71bac
--- /dev/null
+++ b/test/utils/ensureMethod.js
@@ -0,0 +1,10 @@
+var expect = require('chai').expect;
+
+
+module.exports =  function (obj, name) {
+  return function () {
+    expect(obj[name])
+      .to.exist
+      .to.be.an.instanceOf(Function);
+  };
+};
diff --git a/test/utils/ensureProperty.js b/test/utils/ensureProperty.js
new file mode 100644
index 000000000..57333d7c8
--- /dev/null
+++ b/test/utils/ensureProperty.js
@@ -0,0 +1,10 @@
+var expect = require('chai').expect;
+
+
+module.exports =  function (obj, name, cls) {
+  return function () {
+    expect(obj[name])
+      .to.exist
+      .to.be.an.instanceOf(cls);
+  };
+};
diff --git a/test/utils/extractParts.js b/test/utils/extractParts.js
index 5d0ba524d..f2897a56d 100644
--- a/test/utils/extractParts.js
+++ b/test/utils/extractParts.js
@@ -18,3 +18,4 @@ module.exports = function (body, boundary) {
 
   return parts;
 };
+
diff --git a/test/utils/index.js b/test/utils/index.js
index 6c96a1e77..763766fc2 100644
--- a/test/utils/index.js
+++ b/test/utils/index.js
@@ -1,3 +1,5 @@
 module.exports = {
-  extractParts: require('./extractParts')
+  extractParts: require('./extractParts'),
+  ensureProperty: require('./ensureProperty'),
+  ensureMethod: require('./ensureMethod')
 };