diff --git a/.env.example b/.env.example
index 5278a54f..e282046f 100644
--- a/.env.example
+++ b/.env.example
@@ -1,6 +1,7 @@
 APP_ENV=local
 APP_DEBUG=true
 APP_KEY=SomeRandomString
+JWT_SECRET=ChangeMe
 
 DB_HOST=localhost
 DB_PORT=3306
@@ -22,3 +23,7 @@ MAIL_PORT=2525
 MAIL_USERNAME=null
 MAIL_PASSWORD=null
 MAIL_ENCRYPTION=null
+
+API_PREFIX=api
+API_VERSION=v1
+API_DEBUG=false
diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php
index 1ef91c60..d3a4599a 100644
--- a/app/Exceptions/Handler.php
+++ b/app/Exceptions/Handler.php
@@ -55,7 +55,7 @@ class Handler extends ExceptionHandler
             $e = new NotFoundHttpException($e->getMessage(), $e);
         }
 
-        if ($request->isXmlHttpRequest() || $request->ajax() || $request->is('api/*') || $request->is('remote/*')) {
+        if ($request->isXmlHttpRequest() || $request->ajax() || $request->is('remote/*')) {
 
             $exception = 'An exception occured while attempting to perform this action, please try again.';
 
diff --git a/app/Http/Controllers/API/AuthController.php b/app/Http/Controllers/API/AuthController.php
new file mode 100644
index 00000000..6f95aa6c
--- /dev/null
+++ b/app/Http/Controllers/API/AuthController.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Pterodactyl\Http\Controllers\API;
+
+use JWTAuth;
+use Tymon\JWTAuth\Exceptions\JWTException;
+
+use Illuminate\Http\Request;
+use \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
+use \Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
+
+use Pterodactyl\Transformers\UserTransformer;
+use Pterodactyl\Models;
+
+/**
+ * @Resource("Auth", uri="/auth")
+ */
+class AuthController extends BaseController
+{
+
+    /**
+     * Authenticate
+     *
+     * Authenticate with the API to recieved a JSON Web Token
+     *
+     * @Post("/login")
+     * @Versions({"v1"})
+     * @Request({"email": "e@mail.com", "password": "soopersecret"})
+     * @Response(200, body={"token": "<jwt-token>"})
+     */
+    public function postLogin(Request $request) {
+        $credentials = $request->only('email', 'password');
+
+        try {
+            $token = JWTAuth::attempt($credentials, [
+                'permissions' => [
+                    'view_users' => true,
+                    'edit_users' => true,
+                    'delete_users' => false,
+                ]
+            ]);
+            if (!$token) {
+                throw new UnauthorizedHttpException('');
+            }
+        } catch (JWTException $ex) {
+            throw new ServiceUnavailableHttpException('');
+        }
+
+        return compact('token');
+    }
+
+    /**
+     * Check if Authenticated
+     *
+     * @Post("/validate")
+     * @Versions({"v1"})
+     * @Request(headers={"Authorization": "Bearer <jwt-token>"})
+     * @Response(204);
+     */
+    public function postValidate(Request $request) {
+        return $this->response->noContent();
+    }
+
+}
diff --git a/app/Http/Controllers/API/BaseController.php b/app/Http/Controllers/API/BaseController.php
new file mode 100644
index 00000000..b0ade2a6
--- /dev/null
+++ b/app/Http/Controllers/API/BaseController.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Pterodactyl\Http\Controllers\API;
+
+use Dingo\Api\Routing\Helpers;
+use Illuminate\Routing\Controller;
+
+class BaseController extends Controller
+{
+    use Helpers;
+}
diff --git a/app/Http/Controllers/API/UserController.php b/app/Http/Controllers/API/UserController.php
index 958dce88..14657555 100644
--- a/app/Http/Controllers/API/UserController.php
+++ b/app/Http/Controllers/API/UserController.php
@@ -2,82 +2,32 @@
 
 namespace Pterodactyl\Http\Controllers\API;
 
-use Gate;
-use Log;
-use Debugbar;
-use Pterodactyl\Models\API;
-use Pterodactyl\Models\User;
-
-use Pterodactyl\Http\Controllers\Controller;
 use Illuminate\Http\Request;
 
-class UserController extends Controller
+use Pterodactyl\Transformers\UserTransformer;
+use Pterodactyl\Models;
+
+/**
+ * @Resource("Users", uri="/users")
+ */
+class UserController extends BaseController
 {
 
     /**
-     * Constructor
-     */
-    public function __construct()
-    {
-        //
-    }
-
-    public function getAllUsers(Request $request)
-    {
-
-        // Policies don't work if the user isn't logged in for whatever reason in Laravel...
-        if(!API::checkPermission($request->header('X-Authorization'), 'get-users')) {
-            return API::noPermissionError();
-        }
-
-        return response()->json([
-            'users' => User::all()
-        ]);
-    }
-
-    /**
-     * Returns JSON response about a user given their ID.
-     * If fields are provided only those fields are returned.
+     * List All Users
      *
-     * Does not return protected fields (i.e. password & totp_secret)
+     * Lists all users currently on the system.
      *
-     * @param  Request $request
-     * @param  int     $id
-     * @param  string  $fields
-     * @return Response
+     * @Get("/{?page}")
+     * @Versions({"v1"})
+     * @Parameters({
+     * 		@Parameter("page", type="integer", description="The page of results to view.", default=1)
+     * })
+     * @Response(200)
      */
-    public function getUser(Request $request, $id, $fields = null)
-    {
-
-        // Policies don't work if the user isn't logged in for whatever reason in Laravel...
-        if(!API::checkPermission($request->header('X-Authorization'), 'get-users')) {
-            return API::noPermissionError();
-        }
-
-        if (is_null($fields)) {
-            return response()->json(User::find($id));
-        }
-
-        $query = User::where('id', $id);
-        $explode = explode(',', $fields);
-
-        foreach($explode as &$exploded) {
-            if(!empty($exploded)) {
-                $query->addSelect($exploded);
-            }
-        }
-
-        try {
-            return response()->json($query->get());
-        } catch (\Exception $e) {
-            if ($e instanceof \Illuminate\Database\QueryException) {
-                return response()->json([
-                    'error' => 'One of the fields provided in your argument list is invalid.'
-                ], 500);
-            }
-            throw $e;
-        }
-
+    public function getUsers(Request $request) {
+        $users = Models\User::paginate(15);
+        return $this->response->paginator($users, new UserTransformer);
     }
 
 }
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index 5ae0c37a..513fa4d9 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -17,7 +17,6 @@ class Kernel extends HttpKernel
         \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
         \Illuminate\Session\Middleware\StartSession::class,
         \Illuminate\View\Middleware\ShareErrorsFromSession::class,
-        \Pterodactyl\Http\Middleware\VerifyCsrfToken::class,
     ];
 
     /**
@@ -30,7 +29,7 @@ class Kernel extends HttpKernel
         'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
         'guest' => \Pterodactyl\Http\Middleware\RedirectIfAuthenticated::class,
         'server' => \Pterodactyl\Http\Middleware\CheckServer::class,
-        'api' => \Pterodactyl\Http\Middleware\APIAuthenticate::class,
         'admin' => \Pterodactyl\Http\Middleware\AdminAuthenticate::class,
+        'csrf' => \Pterodactyl\Http\Middleware\VerifyCsrfToken::class,
     ];
 }
diff --git a/app/Http/Middleware/APIAuthenticate.php b/app/Http/Middleware/APIAuthenticate.php
deleted file mode 100644
index 8b59f7e2..00000000
--- a/app/Http/Middleware/APIAuthenticate.php
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-namespace Pterodactyl\Http\Middleware;
-
-use Closure;
-use Debugbar;
-
-use Pterodactyl\Models\API;
-
-class APIAuthenticate
-{
-    /**
-     * Handle an incoming request.
-     *
-     * @param  \Illuminate\Http\Request  $request
-     * @param  \Closure  $next
-     * @return mixed
-     */
-    public function handle($request, Closure $next)
-    {
-
-        if(!$request->header('X-Authorization')) {
-            return response()->json([
-                'error' => 'Authorization header was missing with this request. Please pass the \'X-Authorization\' header with your request.'
-            ], 403);
-        }
-
-        $api = API::where('key', $request->header('X-Authorization'))->first();
-        if (!$api) {
-            return response()->json([
-                'error' => 'Invalid API key was provided in the request.'
-            ], 403);
-        }
-
-        if (!is_null($api->allowed_ips)) {
-            if (!in_array($request->ip(), json_decode($api->allowed_ips, true))) {
-                return response()->json([
-                    'error' => 'This IP (' . $request->ip() . ') is not permitted to access the API with that token.'
-                ], 403);
-            }
-        }
-
-        return $next($request);
-
-    }
-}
diff --git a/app/Http/Routes/APIRoutes.php b/app/Http/Routes/APIRoutes.php
new file mode 100644
index 00000000..355a1a3b
--- /dev/null
+++ b/app/Http/Routes/APIRoutes.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Pterodactyl\Http\Routes;
+
+use Pterodactyl\Models;
+use Illuminate\Routing\Router;
+
+class APIRoutes
+{
+
+    public function map(Router $router) {
+
+        app('Dingo\Api\Auth\Auth')->extend('jwt', function ($app) {
+            return new \Dingo\Api\Auth\Provider\JWT($app['Tymon\JWTAuth\JWTAuth']);
+        });
+
+        $api = app('Dingo\Api\Routing\Router');
+
+        $api->version('v1', function ($api) {
+            $api->post('auth/login', [
+                'as' => 'api.auth.login',
+                'uses' => 'Pterodactyl\Http\Controllers\API\AuthController@postLogin'
+            ]);
+
+            $api->post('auth/validate', [
+                'middleware' => 'api.auth',
+                'as' => 'api.auth.validate',
+                'uses' => 'Pterodactyl\Http\Controllers\API\AuthController@postValidate'
+            ]);
+        });
+
+        $api->version('v1', ['middleware' => 'api.auth'], function ($api) {
+
+            $api->get('users', [
+                'as' => 'api.auth.validate',
+                'uses' => 'Pterodactyl\Http\Controllers\API\UserController@getUsers'
+            ]);
+
+            $api->get('users/{id}', function($id) {
+                return Models\User::findOrFail($id);
+            });
+
+
+        });
+    }
+
+}
diff --git a/app/Transformers/UserTransformer.php b/app/Transformers/UserTransformer.php
new file mode 100644
index 00000000..22e4dffe
--- /dev/null
+++ b/app/Transformers/UserTransformer.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Pterodactyl\Transformers;
+
+use Pterodactyl\Models\User;
+use League\Fractal\TransformerAbstract;
+
+class UserTransformer extends TransformerAbstract
+{
+
+    /**
+     * Turn this item object into a generic array
+     *
+     * @return array
+     */
+    public function transform(User $user)
+    {
+        return $user;
+    }
+
+}
diff --git a/composer.json b/composer.json
index 8dd5c598..d56ce983 100644
--- a/composer.json
+++ b/composer.json
@@ -8,12 +8,14 @@
         "php": ">=5.5.9",
         "laravel/framework": "5.2.*",
         "barryvdh/laravel-debugbar": "^2.0",
+        "dingo/api": "1.0.*@dev",
         "doctrine/dbal": "^2.5",
         "guzzlehttp/guzzle": "^6.1",
         "pragmarx/google2fa": "^0.7.1",
         "webpatser/laravel-uuid": "^2.0",
         "prologue/alerts": "^0.4.0",
-        "s1lentium/iptools": "^1.0"
+        "s1lentium/iptools": "^1.0",
+        "tymon/jwt-auth": "^0.5.6"
     },
     "require-dev": {
         "fzaninotto/faker": "~1.4",
diff --git a/config/api.php b/config/api.php
new file mode 100644
index 00000000..87272304
--- /dev/null
+++ b/config/api.php
@@ -0,0 +1,209 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Standards Tree
+    |--------------------------------------------------------------------------
+    |
+    | Versioning an API with Dingo revolves around content negotiation and
+    | custom MIME types. A custom type will belong to one of three
+    | standards trees, the Vendor tree (vnd), the Personal tree
+    | (prs), and the Unregistered tree (x).
+    |
+    | By default the Unregistered tree (x) is used, however, should you wish
+    | to you can register your type with the IANA. For more details:
+    | https://tools.ietf.org/html/rfc6838
+    |
+    */
+
+    'standardsTree' => env('API_STANDARDS_TREE', 'x'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | API Subtype
+    |--------------------------------------------------------------------------
+    |
+    | Your subtype will follow the standards tree you use when used in the
+    | "Accept" header to negotiate the content type and version.
+    |
+    | For example: Accept: application/x.SUBTYPE.v1+json
+    |
+    */
+
+    'subtype' => env('API_SUBTYPE', 'pterodactyl'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Default API Version
+    |--------------------------------------------------------------------------
+    |
+    | This is the default version when strict mode is disabled and your API
+    | is accessed via a web browser. It's also used as the default version
+    | when generating your APIs documentation.
+    |
+    */
+
+    'version' => env('API_VERSION', 'v1'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Default API Prefix
+    |--------------------------------------------------------------------------
+    |
+    | A default prefix to use for your API routes so you don't have to
+    | specify it for each group.
+    |
+    */
+
+    'prefix' => env('API_PREFIX', null),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Default API Domain
+    |--------------------------------------------------------------------------
+    |
+    | A default domain to use for your API routes so you don't have to
+    | specify it for each group.
+    |
+    */
+
+    'domain' => env('API_DOMAIN', null),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Name
+    |--------------------------------------------------------------------------
+    |
+    | When documenting your API using the API Blueprint syntax you can
+    | configure a default name to avoid having to manually specify
+    | one when using the command.
+    |
+    */
+
+    'name' => env('API_NAME', 'Pterodactyl Panel API'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Conditional Requests
+    |--------------------------------------------------------------------------
+    |
+    | Globally enable conditional requests so that an ETag header is added to
+    | any successful response. Subsequent requests will perform a check and
+    | will return a 304 Not Modified. This can also be enabled or disabled
+    | on certain groups or routes.
+    |
+    */
+
+    'conditionalRequest' => env('API_CONDITIONAL_REQUEST', true),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Strict Mode
+    |--------------------------------------------------------------------------
+    |
+    | Enabling strict mode will require clients to send a valid Accept header
+    | with every request. This also voids the default API version, meaning
+    | your API will not be browsable via a web browser.
+    |
+    */
+
+    'strict' => env('API_STRICT', false),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Debug Mode
+    |--------------------------------------------------------------------------
+    |
+    | Enabling debug mode will result in error responses caused by thrown
+    | exceptions to have a "debug" key that will be populated with
+    | more detailed information on the exception.
+    |
+    */
+
+    'debug' => env('API_DEBUG', false),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Generic Error Format
+    |--------------------------------------------------------------------------
+    |
+    | When some HTTP exceptions are not caught and dealt with the API will
+    | generate a generic error response in the format provided. Any
+    | keys that aren't replaced with corresponding values will be
+    | removed from the final response.
+    |
+    */
+
+    'errorFormat' => [
+        'message' => ':message',
+        'errors' => ':errors',
+        'code' => ':code',
+        'status_code' => ':status_code',
+        'debug' => ':debug',
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Authentication Providers
+    |--------------------------------------------------------------------------
+    |
+    | The authentication providers that should be used when attempting to
+    | authenticate an incoming API request.
+    |
+    */
+
+    'auth' => [
+        'jwt' => 'Dingo\Api\Auth\Provider\JWT'
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Throttling / Rate Limiting
+    |--------------------------------------------------------------------------
+    |
+    | Consumers of your API can be limited to the amount of requests they can
+    | make. You can create your own throttles or simply change the default
+    | throttles.
+    |
+    */
+
+    'throttling' => [
+
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Response Transformer
+    |--------------------------------------------------------------------------
+    |
+    | Responses can be transformed so that they are easier to format. By
+    | default a Fractal transformer will be used to transform any
+    | responses prior to formatting. You can easily replace
+    | this with your own transformer.
+    |
+    */
+
+    'transformer' => env('API_TRANSFORMER', Dingo\Api\Transformer\Adapter\Fractal::class),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Response Formats
+    |--------------------------------------------------------------------------
+    |
+    | Responses can be returned in multiple formats by registering different
+    | response formatters. You can also customize an existing response
+    | formatter.
+    |
+    */
+
+    'defaultFormat' => env('API_DEFAULT_FORMAT', 'json'),
+
+    'formats' => [
+
+        'json' => Dingo\Api\Http\Response\Format\Json::class,
+
+    ],
+
+];
diff --git a/config/app.php b/config/app.php
index 5fb65fa0..aa29a7be 100644
--- a/config/app.php
+++ b/config/app.php
@@ -112,6 +112,9 @@ return [
 
     'providers' => [
 
+        Dingo\Api\Provider\LaravelServiceProvider::class,
+        Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,
+
         /*
          * Laravel Framework Service Providers...
          */
@@ -179,6 +182,8 @@ return [
         'Crypt'     => Illuminate\Support\Facades\Crypt::class,
         'DB'        => Illuminate\Support\Facades\DB::class,
         'Debugbar'  => Barryvdh\Debugbar\Facade::class,
+        'DingoAPI'  => Dingo\Api\Facade\API::class,
+        'DingoRoute' => Dingo\Api\Facade\Route::class,
         'Eloquent'  => Illuminate\Database\Eloquent\Model::class,
         'Event'     => Illuminate\Support\Facades\Event::class,
         'File'      => Illuminate\Support\Facades\File::class,
@@ -187,6 +192,8 @@ return [
         'Hash'      => Illuminate\Support\Facades\Hash::class,
         'Input'     => Illuminate\Support\Facades\Input::class,
         'Inspiring' => Illuminate\Foundation\Inspiring::class,
+        'JWTAuth'   => Tymon\JWTAuth\Facades\JWTAuth::class,
+        'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,
         'Lang'      => Illuminate\Support\Facades\Lang::class,
         'Log'       => Illuminate\Support\Facades\Log::class,
         'Mail'      => Illuminate\Support\Facades\Mail::class,
diff --git a/config/jwt.php b/config/jwt.php
new file mode 100644
index 00000000..7c110132
--- /dev/null
+++ b/config/jwt.php
@@ -0,0 +1,168 @@
+<?php
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | JWT Authentication Secret
+    |--------------------------------------------------------------------------
+    |
+    | Don't forget to set this, as it will be used to sign your tokens.
+    | A helper command is provided for this: `php artisan jwt:generate`
+    |
+    */
+
+    'secret' => env('JWT_SECRET', 'changeme'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | JWT time to live
+    |--------------------------------------------------------------------------
+    |
+    | Specify the length of time (in minutes) that the token will be valid for.
+    | Defaults to 1 hour
+    |
+    */
+
+    'ttl' => 60,
+
+    /*
+    |--------------------------------------------------------------------------
+    | Refresh time to live
+    |--------------------------------------------------------------------------
+    |
+    | Specify the length of time (in minutes) that the token can be refreshed
+    | within. I.E. The user can refresh their token within a 2 week window of
+    | the original token being created until they must re-authenticate.
+    | Defaults to 2 weeks
+    |
+    */
+
+    'refresh_ttl' => 20160,
+
+    /*
+    |--------------------------------------------------------------------------
+    | JWT hashing algorithm
+    |--------------------------------------------------------------------------
+    |
+    | Specify the hashing algorithm that will be used to sign the token.
+    |
+    | See here: https://github.com/namshi/jose/tree/2.2.0/src/Namshi/JOSE/Signer
+    | for possible values
+    |
+    */
+
+    'algo' => 'HS256',
+
+    /*
+    |--------------------------------------------------------------------------
+    | User Model namespace
+    |--------------------------------------------------------------------------
+    |
+    | Specify the full namespace to your User model.
+    | e.g. 'Acme\Entities\User'
+    |
+    */
+
+    'user' => 'Pterodactyl\Models\User',
+
+    /*
+    |--------------------------------------------------------------------------
+    | User identifier
+    |--------------------------------------------------------------------------
+    |
+    | Specify a unique property of the user that will be added as the 'sub'
+    | claim of the token payload.
+    |
+    */
+
+    'identifier' => 'id',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Required Claims
+    |--------------------------------------------------------------------------
+    |
+    | Specify the required claims that must exist in any token.
+    | A TokenInvalidException will be thrown if any of these claims are not
+    | present in the payload.
+    |
+    */
+
+    'required_claims' => ['iss', 'iat', 'exp', 'nbf', 'sub', 'jti'],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Blacklist Enabled
+    |--------------------------------------------------------------------------
+    |
+    | In order to invalidate tokens, you must have the the blacklist enabled.
+    | If you do not want or need this functionality, then set this to false.
+    |
+    */
+
+    'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Providers
+    |--------------------------------------------------------------------------
+    |
+    | Specify the various providers used throughout the package.
+    |
+    */
+
+    'providers' => [
+
+        /*
+        |--------------------------------------------------------------------------
+        | User Provider
+        |--------------------------------------------------------------------------
+        |
+        | Specify the provider that is used to find the user based
+        | on the subject claim
+        |
+        */
+
+        'user' => 'Tymon\JWTAuth\Providers\User\EloquentUserAdapter',
+
+        /*
+        |--------------------------------------------------------------------------
+        | JWT Provider
+        |--------------------------------------------------------------------------
+        |
+        | Specify the provider that is used to create and decode the tokens.
+        |
+        */
+
+        'jwt' => 'Tymon\JWTAuth\Providers\JWT\NamshiAdapter',
+
+        /*
+        |--------------------------------------------------------------------------
+        | Authentication Provider
+        |--------------------------------------------------------------------------
+        |
+        | Specify the provider that is used to authenticate users.
+        |
+        */
+
+        'auth' => function ($app) {
+            return new Tymon\JWTAuth\Providers\Auth\IlluminateAuthAdapter($app['auth']);
+        },
+
+        /*
+        |--------------------------------------------------------------------------
+        | Storage Provider
+        |--------------------------------------------------------------------------
+        |
+        | Specify the provider that is used to store tokens in the blacklist
+        |
+        */
+
+        'storage' => function ($app) {
+            return new Tymon\JWTAuth\Providers\Storage\IlluminateCacheAdapter($app['cache']);
+        }
+
+    ]
+
+];