diff --git a/Dockerfile b/Dockerfile
index 82836887a..7fa950b14 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -13,7 +13,7 @@ RUN apt-get update && apt-get -y install \
 # Install requirements separately, because pip understands a git+https url while setuptools doesn't
 RUN pip install -r /tmp/teeth-agent/requirements.txt
 
-# This will succeed because all the dependencies (including pesky teeth_rest) were installed previously
+# This will succeed because all the dependencies were installed previously
 RUN pip install /tmp/teeth-agent
 
 CMD [ "/usr/local/bin/teeth-agent" ]
diff --git a/requirements.txt b/requirements.txt
index 54d08099f..7df51dbf8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,8 +1,8 @@
 Werkzeug==0.9.4
 requests==2.0.0
 stevedore==0.14
--e git+https://github.com/racker/teeth-rest.git@e876c0fddd5ce2f5223ab16936f711b0d57e19c4#egg=teeth_rest
 wsgiref>=0.1.2
 pecan>=0.4.5
 WSME>=0.6
 six>=1.5.2
+structlog==0.4.1
diff --git a/teeth_agent/agent.py b/teeth_agent/agent.py
index 62292fdde..fbc263951 100644
--- a/teeth_agent/agent.py
+++ b/teeth_agent/agent.py
@@ -22,12 +22,11 @@ import time
 import pkg_resources
 from stevedore import driver
 import structlog
-from teeth_rest import encoding
-from teeth_rest import errors as rest_errors
 from wsgiref import simple_server
 
 from teeth_agent.api import app
 from teeth_agent import base
+from teeth_agent import encoding
 from teeth_agent import errors
 from teeth_agent import hardware
 from teeth_agent import overlord_agent_api
@@ -39,7 +38,7 @@ class TeethAgentStatus(encoding.Serializable):
         self.started_at = started_at
         self.version = version
 
-    def serialize(self, view):
+    def serialize(self):
         """Turn the status into a dict."""
         return collections.OrderedDict([
             ('mode', self.mode),
@@ -181,7 +180,7 @@ class TeethAgent(object):
             try:
                 result = self.mode_implementation.execute(command_part,
                                                           **kwargs)
-            except rest_errors.InvalidContentError as e:
+            except errors.InvalidContentError as e:
                 # Any command may raise a InvalidContentError which will be
                 # returned to the caller directly.
                 raise e
diff --git a/teeth_agent/base.py b/teeth_agent/base.py
index 3c55ea026..8147c43d9 100644
--- a/teeth_agent/base.py
+++ b/teeth_agent/base.py
@@ -18,9 +18,8 @@ import threading
 import uuid
 
 import structlog
-from teeth_rest import encoding
-from teeth_rest import errors as rest_errors
 
+from teeth_agent import encoding
 from teeth_agent import errors
 
 
@@ -39,7 +38,7 @@ class BaseCommandResult(encoding.Serializable):
         self.command_error = None
         self.command_result = None
 
-    def serialize(self, view):
+    def serialize(self):
         return dict((
             (u'id', self.id),
             (u'command_name', self.command_name),
@@ -82,9 +81,9 @@ class AsyncCommandResult(BaseCommandResult):
         self.execution_thread = threading.Thread(target=self.run,
                                                  name=thread_name)
 
-    def serialize(self, view):
+    def serialize(self):
         with self.command_state_lock:
-            return super(AsyncCommandResult, self).serialize(view)
+            return super(AsyncCommandResult, self).serialize()
 
     def start(self):
         self.execution_thread.start()
@@ -107,7 +106,7 @@ class AsyncCommandResult(BaseCommandResult):
                 self.command_status = AgentCommandStatus.SUCCEEDED
 
         except Exception as e:
-            if not isinstance(e, rest_errors.RESTError):
+            if not isinstance(e, errors.RESTError):
                 e = errors.CommandExecutionError(str(e))
 
             with self.command_state_lock:
diff --git a/teeth_agent/encoding.py b/teeth_agent/encoding.py
new file mode 100644
index 000000000..a6386579b
--- /dev/null
+++ b/teeth_agent/encoding.py
@@ -0,0 +1,53 @@
+"""
+Copyright 2013 Rackspace, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+import json
+import uuid
+
+
+class Serializable(object):
+    """Base class for things that can be serialized."""
+    def serialize(self):
+        """Turn this object into a dict."""
+        raise NotImplementedError()
+
+
+class RESTJSONEncoder(json.JSONEncoder):
+    """A slightly customized JSON encoder."""
+    def encode(self, o):
+        """Turn an object into JSON.
+
+        Appends a newline to responses when configured to pretty-print,
+        in order to make use of curl less painful from most shells.
+        """
+        delimiter = ''
+
+        # if indent is None, newlines are still inserted, so we should too.
+        if self.indent is not None:
+            delimiter = '\n'
+
+        return super(RESTJSONEncoder, self).encode(o) + delimiter
+
+    def default(self, o):
+        """Turn an object into a serializable object. In particular, by
+        calling :meth:`.Serializable.serialize`.
+        """
+        if isinstance(o, Serializable):
+            return o.serialize()
+        elif isinstance(o, uuid.UUID):
+            return str(o)
+        else:
+            return json.JSONEncoder.default(self, o)
diff --git a/teeth_agent/errors.py b/teeth_agent/errors.py
index 0e1abf1dd..2b32a9033 100644
--- a/teeth_agent/errors.py
+++ b/teeth_agent/errors.py
@@ -14,10 +14,50 @@ See the License for the specific language governing permissions and
 limitations under the License.
 """
 
-from teeth_rest import errors
+import collections
+
+from teeth_agent import encoding
 
 
-class CommandExecutionError(errors.RESTError):
+class RESTError(Exception, encoding.Serializable):
+    """Base class for errors generated in teeth."""
+    message = 'An error occurred'
+    details = 'An unexpected error occurred. Please try back later.'
+    status_code = 500
+
+    def serialize(self):
+        """Turn a RESTError into a dict."""
+        return collections.OrderedDict([
+            ('type', self.__class__.__name__),
+            ('code', self.status_code),
+            ('message', self.message),
+            ('details', self.details),
+        ])
+
+
+class InvalidContentError(RESTError):
+    """Error which occurs when a user supplies invalid content, either
+    because that content cannot be parsed according to the advertised
+    `Content-Type`, or due to a content validation error.
+    """
+    message = 'Invalid request body'
+    status_code = 400
+
+    def __init__(self, details):
+        self.details = details
+
+
+class NotFound(RESTError):
+    """Error which occurs when a user supplies invalid content, either
+    because that content cannot be parsed according to the advertised
+    `Content-Type`, or due to a content validation error.
+    """
+    message = 'Not found'
+    status_code = 404
+    details = 'The requested URL was not found.'
+
+
+class CommandExecutionError(RESTError):
     """Error raised when a command fails to execute."""
 
     message = 'Command execution failed'
@@ -27,7 +67,7 @@ class CommandExecutionError(errors.RESTError):
         self.details = details
 
 
-class InvalidCommandError(errors.InvalidContentError):
+class InvalidCommandError(InvalidContentError):
     """Error which is raised when an unknown command is issued."""
 
     messsage = 'Invalid command'
@@ -36,7 +76,7 @@ class InvalidCommandError(errors.InvalidContentError):
         super(InvalidCommandError, self).__init__(details)
 
 
-class InvalidCommandParamsError(errors.InvalidContentError):
+class InvalidCommandParamsError(InvalidContentError):
     """Error which is raised when command parameters are invalid."""
 
     message = 'Invalid command parameters'
@@ -45,14 +85,14 @@ class InvalidCommandParamsError(errors.InvalidContentError):
         super(InvalidCommandParamsError, self).__init__(details)
 
 
-class RequestedObjectNotFoundError(errors.NotFound):
+class RequestedObjectNotFoundError(NotFound):
     def __init__(self, type_descr, obj_id):
         details = '{} with id {} not found.'.format(type_descr, obj_id)
         super(RequestedObjectNotFoundError, self).__init__(details)
         self.details = details
 
 
-class OverlordAPIError(errors.RESTError):
+class OverlordAPIError(RESTError):
     """Error raised when a call to the agent API fails."""
 
     message = 'Error in call to teeth-agent-api.'
@@ -71,7 +111,7 @@ class HeartbeatError(OverlordAPIError):
         super(HeartbeatError, self).__init__(details)
 
 
-class ImageDownloadError(errors.RESTError):
+class ImageDownloadError(RESTError):
     """Error raised when an image cannot be downloaded."""
 
     message = 'Error downloading image.'
@@ -81,7 +121,7 @@ class ImageDownloadError(errors.RESTError):
         self.details = 'Could not download image with id {}.'.format(image_id)
 
 
-class ImageChecksumError(errors.RESTError):
+class ImageChecksumError(RESTError):
     """Error raised when an image fails to verify against its checksum."""
 
     message = 'Error verifying image checksum.'
@@ -92,7 +132,7 @@ class ImageChecksumError(errors.RESTError):
         self.details = self.details.format(image_id)
 
 
-class ImageWriteError(errors.RESTError):
+class ImageWriteError(RESTError):
     """Error raised when an image cannot be written to a device."""
 
     message = 'Error writing image to device.'
@@ -103,7 +143,7 @@ class ImageWriteError(errors.RESTError):
         self.details = self.details.format(device, exit_code)
 
 
-class ConfigDriveWriteError(errors.RESTError):
+class ConfigDriveWriteError(RESTError):
     """Error raised when a configdrive directory cannot be written to a
     device.
     """
@@ -117,7 +157,7 @@ class ConfigDriveWriteError(errors.RESTError):
         self.details = details
 
 
-class SystemRebootError(errors.RESTError):
+class SystemRebootError(RESTError):
     """Error raised when a system cannot reboot."""
 
     message = 'Error rebooting system.'
diff --git a/teeth_agent/hardware.py b/teeth_agent/hardware.py
index bc79db391..a8e4e0fd9 100644
--- a/teeth_agent/hardware.py
+++ b/teeth_agent/hardware.py
@@ -22,7 +22,7 @@ import subprocess
 import stevedore
 import structlog
 
-from teeth_rest import encoding
+from teeth_agent import encoding
 
 _global_manager = None
 
@@ -49,7 +49,7 @@ class HardwareInfo(encoding.Serializable):
         self.type = type
         self.id = id
 
-    def serialize(self, view):
+    def serialize(self):
         return collections.OrderedDict([
             ('type', self.type),
             ('id', self.id),
diff --git a/teeth_agent/logging.py b/teeth_agent/logging.py
index 1429f9932..5c66bc900 100644
--- a/teeth_agent/logging.py
+++ b/teeth_agent/logging.py
@@ -38,7 +38,7 @@ def _format_event(logger, method, event):
     have enough keys to format.
     """
     if 'event' not in event:
-        # nothing to format, e.g. _log_request in teeth_rest/component
+        # nothing to format
         return event
     # Get a list of fields that need to be filled.
     formatter = string.Formatter()
diff --git a/teeth_agent/overlord_agent_api.py b/teeth_agent/overlord_agent_api.py
index 7fa41ade5..969d375cb 100644
--- a/teeth_agent/overlord_agent_api.py
+++ b/teeth_agent/overlord_agent_api.py
@@ -17,8 +17,8 @@ limitations under the License.
 import json
 
 import requests
-from teeth_rest import encoding
 
+from teeth_agent import encoding
 from teeth_agent import errors
 
 
@@ -28,8 +28,7 @@ class APIClient(object):
     def __init__(self, api_url):
         self.api_url = api_url.rstrip('/')
         self.session = requests.Session()
-        self.encoder = encoding.RESTJSONEncoder(
-            encoding.SerializationViews.PUBLIC)
+        self.encoder = encoding.RESTJSONEncoder()
 
     def _request(self, method, path, data=None):
         request_url = '{api_url}{path}'.format(api_url=self.api_url, path=path)
diff --git a/teeth_agent/tests/agent.py b/teeth_agent/tests/agent.py
index 33b011267..83313606f 100644
--- a/teeth_agent/tests/agent.py
+++ b/teeth_agent/tests/agent.py
@@ -22,10 +22,10 @@ import mock
 import pkg_resources
 from wsgiref import simple_server
 
-from teeth_rest import encoding
 
 from teeth_agent import agent
 from teeth_agent import base
+from teeth_agent import encoding
 from teeth_agent import errors
 from teeth_agent import hardware
 
@@ -118,9 +118,7 @@ class TestHeartbeater(unittest.TestCase):
 
 class TestBaseAgent(unittest.TestCase):
     def setUp(self):
-        self.encoder = encoding.RESTJSONEncoder(
-            encoding.SerializationViews.PUBLIC,
-            indent=4)
+        self.encoder = encoding.RESTJSONEncoder(indent=4)
         self.agent = agent.TeethAgent('https://fake_api.example.org:8081/',
                                       ('localhost', 9999),
                                       '192.168.1.1')
diff --git a/teeth_agent/tests/api.py b/teeth_agent/tests/api.py
index 655bbae1c..bbcf47e29 100644
--- a/teeth_agent/tests/api.py
+++ b/teeth_agent/tests/api.py
@@ -22,7 +22,6 @@ import unittest
 from werkzeug import test
 from werkzeug import wrappers
 
-from teeth_rest import encoding
 
 from teeth_agent import agent
 from teeth_agent.api import app
@@ -99,7 +98,7 @@ class TestTeethAPI(unittest.TestCase):
         self.assertEqual(kwargs, {'key': 'value'})
         self.assertEqual(response.status_code, 200)
         data = json.loads(response.data)
-        expected_result = result.serialize(encoding.SerializationViews.PUBLIC)
+        expected_result = result.serialize()
         self.assertEqual(data, expected_result)
 
     def test_execute_agent_command_validation(self):
@@ -150,7 +149,7 @@ class TestTeethAPI(unittest.TestCase):
         self.assertEqual(response.status_code, 200)
         self.assertEqual(json.loads(response.data), {
             u'commands': [
-                cmd_result.serialize(None),
+                cmd_result.serialize(),
             ],
         })
 
@@ -160,8 +159,7 @@ class TestTeethAPI(unittest.TestCase):
                                             True,
                                             {'test': 'result'})
 
-        serialized_cmd_result = cmd_result.serialize(
-            encoding.SerializationViews.PUBLIC)
+        serialized_cmd_result = cmd_result.serialize()
 
         mock_agent = mock.create_autospec(agent.TeethAgent)
         mock_agent.get_command_result.return_value = cmd_result