diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py
index 3b37833..fcd79b3 100644
--- a/IPython/html/base/handlers.py
+++ b/IPython/html/base/handlers.py
@@ -218,10 +218,6 @@ class IPythonHandler(AuthenticatedHandler):
         return self.settings['session_manager']
     
     @property
-    def content_manager(self):
-        return self.settings['content_manager']
-    
-    @property
     def project(self):
         return self.notebook_manager.notebook_dir
     
diff --git a/IPython/html/services/kernels/tests/test_kernels_api.py b/IPython/html/services/kernels/tests/test_kernels_api.py
index e144085..1322878 100644
--- a/IPython/html/services/kernels/tests/test_kernels_api.py
+++ b/IPython/html/services/kernels/tests/test_kernels_api.py
@@ -16,11 +16,15 @@ class KernelAPITest(NotebookTestBase):
     def base_url(self):
         return super(KernelAPITest,self).base_url() + 'api/kernels'
 
+    def mkkernel(self):
+        r = requests.post(self.base_url())
+        return r.json()
+
     def test_no_kernels(self):
         """Make sure there are no kernels running at the start"""
         url = self.base_url()
         r = requests.get(url)
-        assert r.json() == []
+        self.assertEqual(r.json(), [])
 
     def test_main_kernel_handler(self):
         # POST request
@@ -33,4 +37,17 @@ class KernelAPITest(NotebookTestBase):
         assert isinstance(r.json(), list)
         self.assertEqual(r.json()[0], data['id'])
 
-        
\ No newline at end of file
+    def test_kernel_handler(self):
+        # GET kernel with id
+        data = self.mkkernel()
+        url = self.base_url() +'/' + data['id']
+        r = requests.get(url)
+        assert isinstance(r.json(), dict)
+        self.assertIn('id', r.json())
+        self.assertEqual(r.json()['id'], data['id'])
+        
+        # DELETE kernel with id
+        r = requests.delete(url)
+        self.assertEqual(r.status_code, 204)
+        r = requests.get(self.base_url())
+        self.assertEqual(r.json(), [])
\ No newline at end of file
diff --git a/IPython/html/services/notebooks/filenbmanager.py b/IPython/html/services/notebooks/filenbmanager.py
index 2391e25..326bf2f 100644
--- a/IPython/html/services/notebooks/filenbmanager.py
+++ b/IPython/html/services/notebooks/filenbmanager.py
@@ -91,7 +91,7 @@ class FileNotebookManager(NotebookManager):
             notebooks.append(model)
         return notebooks
 
-    def change_notebook(self, data, notebook_name, notebook_path='/'):
+    def update_notebook(self, data, notebook_name, notebook_path='/'):
         """Changes notebook"""
         changes = data.keys()
         for change in changes:
diff --git a/IPython/html/services/notebooks/handlers.py b/IPython/html/services/notebooks/handlers.py
index be64528..40ae7e3 100644
--- a/IPython/html/services/notebooks/handlers.py
+++ b/IPython/html/services/notebooks/handlers.py
@@ -16,127 +16,117 @@ Authors:
 # Imports
 #-----------------------------------------------------------------------------
 
-from tornado import web
+import json
 
-from zmq.utils import jsonapi
+from tornado import web
 
+from ...utils import url_path_join
 from IPython.utils.jsonutil import date_default
 
-from ...base.handlers import IPythonHandler
+from ...base.handlers import IPythonHandler, json_errors
 
 #-----------------------------------------------------------------------------
 # Notebook web service handlers
 #-----------------------------------------------------------------------------
 
 
-class NotebookRootHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        """get returns a list of notebooks from the location
-        where the server was started."""
-        nbm = self.notebook_manager
-        notebooks = nbm.list_notebooks("/")
-        self.finish(jsonapi.dumps(notebooks))
-
-    @web.authenticated
-    def post(self):
-        """post creates a notebooks in the directory where the
-        server was started"""
-        nbm = self.notebook_manager
-        self.log.info(nbm.notebook_dir)
-        body = self.request.body.strip()
-        format = self.get_argument('format', default='json')
-        name = self.get_argument('name', default=None)
-        if body:
-            fname = nbm.save_new_notebook(body, notebook_path='/', name=name, format=format)
-        else:
-            fname = nbm.new_notebook(notebook_path='/')
-        self.set_header('Location', nbm.notebook_dir + fname)
-        model = nbm.notebook_model(fname)
-        self.set_header('Location', '{0}api/notebooks/{1}'.format(self.base_project_url, fname))
-        self.finish(jsonapi.dumps(model))
-
 class NotebookHandler(IPythonHandler):
 
-    SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH', 'POST','DELETE')
+    SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
+
+    def notebook_location(self, name, path):
+        """Return the full URL location of a notebook based.
+        
+        Parameters
+        ----------
+        name : unicode
+            The name of the notebook like "foo.ipynb".
+        path : unicode
+            The URL path of the notebook.
+        """
+        return url_path_join(self.base_project_url, u'/api/notebooks', path, name)
 
     @web.authenticated
+    @json_errors
     def get(self, notebook_path):
         """get checks if a notebook is not named, an returns a list of notebooks
         in the notebook path given. If a name is given, return 
         the notebook representation"""
         nbm = self.notebook_manager
+        # path will have leading and trailing slashes, such as '/foo/bar/'
         name, path = nbm.named_notebook_path(notebook_path)
         
         # Check to see if a notebook name was given
         if name is None:
             # List notebooks in 'notebook_path'
             notebooks = nbm.list_notebooks(path)
-            self.finish(jsonapi.dumps(notebooks))
+            self.finish(json.dumps(notebooks, default=date_default))
         else:
             # get and return notebook representation
-            format = self.get_argument('format', default='json')
-            download = self.get_argument('download', default='False')
-            model = nbm.notebook_model(name, path)
-            last_mod, representation, name = nbm.get_notebook(name, path, format)
-            self.set_header('Last-Modified', last_mod)
-            
-            if download == 'True':
-                if format == u'json':
-                    self.set_header('Content-Type', 'application/json')
-                    self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
-                    self.finish(representation)
-                elif format == u'py':
-                    self.set_header('Content-Type', 'application/x-python')
-                    self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
-                    self.finish(representation)
-            else:
-                self.finish(jsonapi.dumps(model))
+            model = nbm.get_notebook_model(name, path)
+            self.set_header(u'Last-Modified', model[u'last_modified'])
+            self.finish(json.dumps(model, default=date_default))
 
     @web.authenticated
+   # @json_errors
     def patch(self, notebook_path):
         """patch is currently used strictly for notebook renaming.
         Changes the notebook name to the name given in data."""
         nbm = self.notebook_manager
+        # path will have leading and trailing slashes, such as '/foo/bar/'
         name, path = nbm.named_notebook_path(notebook_path)
-        data = jsonapi.loads(self.request.body)
-        model = nbm.change_notebook(data, name, path)
-        self.finish(jsonapi.dumps(model))
+        if name is None:
+            raise web.HTTPError(400, u'Notebook name missing')
+        model = self.get_json_body()
+        if model is None:
+            raise web.HTTPError(400, u'JSON body missing')
+        model = nbm.update_notebook_model(model, name, path)
+        if model[u'name'] != name or model[u'path'] != path:
+            self.set_status(301)
+            location = self.notebook_location(model[u'name'], model[u'path'])
+            self.set_header(u'Location', location)
+        self.set_header(u'Last-Modified', model[u'last_modified'])
+        self.finish(json.dumps(model, default=date_default))
 
     @web.authenticated
-    def post(self,notebook_path):
+    @json_errors
+    def post(self, notebook_path):
         """Create a new notebook in the location given by 'notebook_path'."""
         nbm = self.notebook_manager
-        fname, path = nbm.named_notebook_path(notebook_path)
-        body = self.request.body.strip()
-        format = self.get_argument('format', default='json')
-        name = self.get_argument('name', default=None)
-        if body:
-            fname = nbm.save_new_notebook(body, notebook_path=path, name=name, format=format)
-        else:
-            fname = nbm.new_notebook(notebook_path=path)
-        self.set_header('Location', nbm.notebook_dir + path + fname)
-        model = nbm.notebook_model(fname, path)
-        self.finish(jsonapi.dumps(model))
+        # path will have leading and trailing slashes, such as '/foo/bar/'
+        name, path = nbm.named_notebook_path(notebook_path)
+        model = self.get_json_body()
+        if name is not None:
+            raise web.HTTPError(400, 'No name can be provided when POSTing a new notebook.')
+        model = nbm.create_notebook_model(model, path)
+        location = nbm.notebook_dir + model[u'path'] + model[u'name']
+        location = self.notebook_location(model[u'name'], model[u'path'])
+        self.set_header(u'Location', location)
+        self.set_header(u'Last-Modified', model[u'last_modified'])
+        self.set_status(201)
+        self.finish(json.dumps(model, default=date_default))
 
     @web.authenticated
+    @json_errors
     def put(self, notebook_path):
         """saves the notebook in the location given by 'notebook_path'."""
         nbm = self.notebook_manager
+        # path will have leading and trailing slashes, such as '/foo/bar/'
         name, path = nbm.named_notebook_path(notebook_path)
-        format = self.get_argument('format', default='json')
-        nbm.save_notebook(self.request.body, notebook_path=path, name=name, format=format)
-        model = nbm.notebook_model(name, path)
-        self.set_status(204)
-        self.finish(jsonapi.dumps(model))
+        model = self.get_json_body()
+        if model is None:
+            raise web.HTTPError(400, u'JSON body missing')
+        nbm.save_notebook_model(model, name, path)
+        self.finish(json.dumps(model, default=date_default))
 
     @web.authenticated
+    @json_errors
     def delete(self, notebook_path):
-        """delete rmoves the notebook in the given notebook path"""
+        """delete the notebook in the given notebook path"""
         nbm = self.notebook_manager
+        # path will have leading and trailing slashes, such as '/foo/bar/'
         name, path = nbm.named_notebook_path(notebook_path)
-        nbm.delete_notebook(name, path)
+        nbm.delete_notebook_model(name, path)
         self.set_status(204)
         self.finish()
 
@@ -146,29 +136,28 @@ class NotebookCheckpointsHandler(IPythonHandler):
     SUPPORTED_METHODS = ('GET', 'POST')
     
     @web.authenticated
+    @json_errors
     def get(self, notebook_path):
         """get lists checkpoints for a notebook"""
         nbm = self.notebook_manager
+        # path will have leading and trailing slashes, such as '/foo/bar/'
         name, path = nbm.named_notebook_path(notebook_path)
         checkpoints = nbm.list_checkpoints(name, path)
-        data = jsonapi.dumps(checkpoints, default=date_default)
+        data = json.dumps(checkpoints, default=date_default)
         self.finish(data)
     
     @web.authenticated
+    @json_errors
     def post(self, notebook_path):
         """post creates a new checkpoint"""
         nbm = self.notebook_manager
         name, path = nbm.named_notebook_path(notebook_path)
+        # path will have leading and trailing slashes, such as '/foo/bar/'
         checkpoint = nbm.create_checkpoint(name, path)
-        data = jsonapi.dumps(checkpoint, default=date_default)
-        if path == None:
-            self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
-                self.base_project_url, name, checkpoint['checkpoint_id']
-                ))
-        else:
-            self.set_header('Location', '{0}notebooks/{1}/{2}/checkpoints/{3}'.format(
-                self.base_project_url, path, name, checkpoint['checkpoint_id']
-                ))
+        data = json.dumps(checkpoint, default=date_default)
+        location = url_path_join(self.base_project_url, u'/api/notebooks',
+            path, name, '/checkpoints', checkpoint[u'checkpoint_id'])
+        self.set_header(u'Location', location)
         self.finish(data)
 
 
@@ -177,20 +166,24 @@ class ModifyNotebookCheckpointsHandler(IPythonHandler):
     SUPPORTED_METHODS = ('POST', 'DELETE')
     
     @web.authenticated
+    @json_errors
     def post(self, notebook_path, checkpoint_id):
         """post restores a notebook from a checkpoint"""
         nbm = self.notebook_manager
+        # path will have leading and trailing slashes, such as '/foo/bar/'
         name, path = nbm.named_notebook_path(notebook_path)
-        nbm.restore_checkpoint(name, checkpoint_id, path)
+        nbm.restore_checkpoint(checkpoint_id, name, path)
         self.set_status(204)
         self.finish()
     
     @web.authenticated
+    @json_errors
     def delete(self, notebook_path, checkpoint_id):
         """delete clears a checkpoint for a given notebook"""
         nbm = self.notebook_manager
+        # path will have leading and trailing slashes, such as '/foo/bar/'
         name, path = nbm.named_notebook_path(notebook_path)
-        nbm.delete_checkpoint(name, checkpoint_id, path)
+        nbm.delete_checkpoint(checkpoint_id, name, path)
         self.set_status(204)
         self.finish()
         
@@ -199,19 +192,15 @@ class ModifyNotebookCheckpointsHandler(IPythonHandler):
 #-----------------------------------------------------------------------------
 
 
-_notebook_path_regex = r"(?P<notebook_path>.+)"
+_notebook_path_regex = r"(?P<notebook_path>.*)"
 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
 
 default_handlers = [
-    (r"api/notebooks/%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
-    (r"api/notebooks/%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
+    (r"/api/notebooks/%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
+    (r"/api/notebooks/%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
         ModifyNotebookCheckpointsHandler),
-    (r"api/notebooks/%s/" % _notebook_path_regex, NotebookHandler), 
-    (r"api/notebooks/%s" % _notebook_path_regex, NotebookHandler),
-    (r"api/notebooks/", NotebookRootHandler),
-    (r"api/notebooks", NotebookRootHandler),
+    (r"/api/notebooks%s" % _notebook_path_regex, NotebookHandler),
 ]
 
 
 
-
diff --git a/IPython/html/services/notebooks/nbmanager.py b/IPython/html/services/notebooks/nbmanager.py
index d610aad..4ac2598 100644
--- a/IPython/html/services/notebooks/nbmanager.py
+++ b/IPython/html/services/notebooks/nbmanager.py
@@ -155,12 +155,12 @@ class NotebookManager(LoggingConfigurable):
         """
         raise NotImplementedError('must be implemented in a subclass')
 
-    def notebook_model(self, notebook_name, notebook_path='/', content=True):
+    def notebook_model(self, name, path='/', content=True):
         """ Creates the standard notebook model """
-        last_modified, contents = self.read_notebook_object(notebook_name, notebook_path)
-        model = {"name": notebook_name, 
-                    "path": notebook_path,
-                    "last_modified (UTC)": last_modified.ctime()}
+        last_modified, contents = self.read_notebook_model(name, path)
+        model = {"name": name, 
+                    "path": path,
+                    "last_modified": last_modified.ctime()}
         if content is True:
             model['content'] = contents
         return model
@@ -180,10 +180,22 @@ class NotebookManager(LoggingConfigurable):
         name = nb.metadata.get('name', 'notebook')
         return last_mod, representation, name
 
-    def read_notebook_object(self, notebook_name, notebook_path='/'):
+    def read_notebook_model(self, notebook_name, notebook_path='/'):
         """Get the object representation of a notebook by notebook_id."""
         raise NotImplementedError('must be implemented in a subclass')
 
+    def save_notebook(self, model, name=None, path='/'):
+        """Save the Notebook"""
+        if name is None:
+            name = self.increment_filename('Untitled', path)
+        if 'content' not in model:
+            metadata = current.new_metadata(name=name)
+            nb = current.new_notebook(metadata=metadata)
+        else:
+            nb = model['content']
+        self.write_notebook_object()
+            
+
     def save_new_notebook(self, data, notebook_path='/', name=None, format=u'json'):
         """Save a new notebook and return its name.
 
@@ -208,7 +220,7 @@ class NotebookManager(LoggingConfigurable):
         notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
         return notebook_name
 
-    def save_notebook(self, data, notebook_path='/', name=None, new_name=None, format=u'json'):
+    def save_notebook(self, data, notebook_path='/', name=None, format=u'json'):
         """Save an existing notebook by notebook_name."""
         if format not in self.allowed_formats:
             raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
@@ -222,7 +234,7 @@ class NotebookManager(LoggingConfigurable):
             nb.metadata.name = name
         self.write_notebook_object(nb, name, notebook_path, new_name)
 
-    def write_notebook_object(self, nb, notebook_name='/', notebook_path='/', new_name=None):
+    def write_notebook_model(self, model):
         """Write a notebook object and return its notebook_name.
 
         If notebook_name is None, this method should create a new notebook_name.
diff --git a/IPython/html/services/notebooks/tests/test_notebooks_api.py b/IPython/html/services/notebooks/tests/test_notebooks_api.py
index f11cb3f..6d44a93 100644
--- a/IPython/html/services/notebooks/tests/test_notebooks_api.py
+++ b/IPython/html/services/notebooks/tests/test_notebooks_api.py
@@ -25,15 +25,15 @@ class APITest(NotebookTestBase):
         r = requests.delete(url)
         return r.status_code
 
-    def test_notebook_root_handler(self):
+    def test_notebook_handler(self):
         # POST a notebook and test the dict thats returned.
         #url, nb = self.mknb()
         url = self.notebook_url()
         nb = requests.post(url)
         data = nb.json()
         assert isinstance(data, dict)
-        assert data.has_key("name")
-        assert data.has_key("path")
+        self.assertIn('name', data)
+        self.assertIn('path', data)
         self.assertEqual(data['name'], u'Untitled0.ipynb')
         self.assertEqual(data['path'], u'/')
 
@@ -43,8 +43,7 @@ class APITest(NotebookTestBase):
         assert isinstance(r.json()[0], dict)
         
         self.delnb('Untitled0.ipynb')
-
-    def test_notebook_handler(self):
+        
         # GET with a notebook name.
         url, nb = self.mknb()
         data = nb.json()
@@ -79,18 +78,18 @@ class APITest(NotebookTestBase):
         data2 = nb2.json()
         assert isinstance(data, dict)
         assert isinstance(data2, dict)
-        assert data.has_key("name")
-        assert data.has_key("path")
+        self.assertIn('name', data)
+        self.assertIn('path', data)
         self.assertEqual(data['name'], u'Untitled0.ipynb')
         self.assertEqual(data['path'], u'/foo/')
-        assert data2.has_key("name")
-        assert data2.has_key("path")
+        self.assertIn('name', data2)
+        self.assertIn('path', data2)
         self.assertEqual(data2['name'], u'Untitled0.ipynb')
         self.assertEqual(data2['path'], u'/foo/bar/')
 
         # GET request on notebooks one and two levels down.
-        r = requests.get(url+'Untitled0.ipynb')
-        r2 = requests.get(url2+'Untitled0.ipynb')
+        r = requests.get(url+'/Untitled0.ipynb')
+        r2 = requests.get(url2+'/Untitled0.ipynb')
         assert isinstance(r.json(), dict)
         self.assertEqual(r.json(), data)
         assert isinstance(r2.json(), dict)
@@ -98,13 +97,13 @@ class APITest(NotebookTestBase):
 
         # PATCH notebooks that are one and two levels down.
         new_name = {'name': 'testfoo.ipynb'}
-        r = requests.patch(url+'Untitled0.ipynb', data=jsonapi.dumps(new_name))
-        r = requests.get(url+'testfoo.ipynb')
+        r = requests.patch(url+'/Untitled0.ipynb', data=jsonapi.dumps(new_name))
+        r = requests.get(url+'/testfoo.ipynb')
         data = r.json()
         assert isinstance(data, dict)
-        assert data.has_key('name')
+        self.assertIn('name', data)
         self.assertEqual(data['name'], 'testfoo.ipynb')
-        r = requests.get(url+'Untitled0.ipynb')
+        r = requests.get(url+'/Untitled0.ipynb')
         self.assertEqual(r.status_code, 404)
         
         # DELETE notebooks
diff --git a/IPython/html/services/sessions/tests/test_sessions_api.py b/IPython/html/services/sessions/tests/test_sessions_api.py
index b781387..eca503a 100644
--- a/IPython/html/services/sessions/tests/test_sessions_api.py
+++ b/IPython/html/services/sessions/tests/test_sessions_api.py
@@ -43,7 +43,7 @@ class SessionAPITest(NotebookTestBase):
         r = requests.post(self.session_url(), params=param)
         data = r.json()
         assert isinstance(data, dict)
-        assert data.has_key('name')
+        self.assertIn('name', data)
         self.assertEqual(data['name'], notebook['name'])
 
         # GET sessions
@@ -79,8 +79,8 @@ class SessionAPITest(NotebookTestBase):
         requests.patch(self.notebook_url() + '/Untitled0.ipynb', 
             data=jsonapi.dumps({'name':'test.ipynb'}))
         assert isinstance(r.json(), dict)
-        assert r.json().has_key('name')
-        assert r.json().has_key('id')
+        self.assertIn('name', r.json())
+        self.assertIn('id', r.json())
         self.assertEqual(r.json()['name'], 'test.ipynb')
         self.assertEqual(r.json()['id'], session['id'])
 
@@ -88,8 +88,8 @@ class SessionAPITest(NotebookTestBase):
         r = requests.delete(sess_url)
         self.assertEqual(r.status_code, 204)
         r = requests.get(self.session_url())
-        assert r.json() == []
+        self.assertEqual(r.json(), [])
         
         # Clean up
         r = self.delnb('test.ipynb')
-        assert r == 204
\ No newline at end of file
+        self.assertEqual(r, 204)
\ No newline at end of file
diff --git a/IPython/html/tests/launchnotebook.py b/IPython/html/tests/launchnotebook.py
index f93e209..0cc195c 100644
--- a/IPython/html/tests/launchnotebook.py
+++ b/IPython/html/tests/launchnotebook.py
@@ -2,6 +2,7 @@
 
 import sys
 import time
+import requests
 from subprocess import Popen, PIPE
 from unittest import TestCase
 
@@ -17,6 +18,26 @@ class NotebookTestBase(TestCase):
 
     port = 1234
 
+    def wait_till_alive(self):
+        url = 'http://localhost:%i/' % self.port
+        while True:
+            time.sleep(.1)
+            try:
+                r = requests.get(url + 'api/notebooks')
+                break
+            except requests.exceptions.ConnectionError:
+                pass
+        
+    def wait_till_dead(self):
+        url = 'http://localhost:%i/' % self.port
+        while True:
+            time.sleep(.1)
+            try:
+                r = requests.get(url + 'api/notebooks')
+                continue
+            except requests.exceptions.ConnectionError:
+                break
+
     def setUp(self):
         self.ipython_dir = TemporaryDirectory()
         self.notebook_dir = TemporaryDirectory()
@@ -27,15 +48,16 @@ class NotebookTestBase(TestCase):
             '--no-browser',
             '--ipython-dir=%s' % self.ipython_dir.name,
             '--notebook-dir=%s' % self.notebook_dir.name
-        ]        
+        ]  
         self.notebook = Popen(notebook_args, stdout=PIPE, stderr=PIPE)
-        time.sleep(3.0)
+        self.wait_till_alive()
+        #time.sleep(3.0)
 
     def tearDown(self):
         self.notebook.terminate()
         self.ipython_dir.cleanup()
         self.notebook_dir.cleanup()
-        time.sleep(3.0)
-
+        self.wait_till_dead()
+        
     def base_url(self):
         return 'http://localhost:%i/' % self.port