diff --git a/tailbone/api/__init__.py b/tailbone/api/__init__.py
index 6c310fa7..d09c669a 100644
--- a/tailbone/api/__init__.py
+++ b/tailbone/api/__init__.py
@@ -27,7 +27,10 @@ Tailbone Web API
 from __future__ import unicode_literals, absolute_import
 
 from .core import APIView, api
+from .master import APIMasterView
 
 
 def includeme(config):
     config.include('tailbone.api.auth')
+    config.include('tailbone.api.customers')
+    config.include('tailbone.api.users')
diff --git a/tailbone/api/customers.py b/tailbone/api/customers.py
new file mode 100644
index 00000000..75f1b438
--- /dev/null
+++ b/tailbone/api/customers.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8; -*-
+################################################################################
+#
+#  Rattail -- Retail Software Framework
+#  Copyright © 2010-2018 Lance Edgar
+#
+#  This file is part of Rattail.
+#
+#  Rattail is free software: you can redistribute it and/or modify it under the
+#  terms of the GNU General Public License as published by the Free Software
+#  Foundation, either version 3 of the License, or (at your option) any later
+#  version.
+#
+#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
+#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+#  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+#  details.
+#
+#  You should have received a copy of the GNU General Public License along with
+#  Rattail.  If not, see .
+#
+################################################################################
+"""
+Tailbone Web API - Customer Views
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+from rattail.db import model
+
+from tailbone.api import APIMasterView
+
+
+class CustomerView(APIMasterView):
+
+    model_class = model.Customer
+
+    def normalize(self, customer):
+        return {
+            'id': customer.id,
+            'name': customer.name,
+        }
+
+
+def includeme(config):
+    CustomerView.defaults(config)
diff --git a/tailbone/api/master.py b/tailbone/api/master.py
new file mode 100644
index 00000000..ff3261c9
--- /dev/null
+++ b/tailbone/api/master.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8; -*-
+################################################################################
+#
+#  Rattail -- Retail Software Framework
+#  Copyright © 2010-2018 Lance Edgar
+#
+#  This file is part of Rattail.
+#
+#  Rattail is free software: you can redistribute it and/or modify it under the
+#  terms of the GNU General Public License as published by the Free Software
+#  Foundation, either version 3 of the License, or (at your option) any later
+#  version.
+#
+#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
+#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+#  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+#  details.
+#
+#  You should have received a copy of the GNU General Public License along with
+#  Rattail.  If not, see .
+#
+################################################################################
+"""
+Tailbone Web API - Master View
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+from tailbone.api import APIView, api
+from tailbone.db import Session
+
+
+class APIMasterView(APIView):
+
+    @classmethod
+    def get_model_class(cls):
+        if hasattr(cls, 'model_class'):
+            return cls.model_class
+        raise NotImplementedError("must set `model_class` for {}".format(cls.__name__))
+
+    @classmethod
+    def get_model_key(cls):
+        if hasattr(cls, 'model_key'):
+            return cls.model_name
+        return cls.get_model_class().__name__.lower()
+
+    @classmethod
+    def get_model_key_plural(cls):
+        if hasattr(cls, 'model_key_plural'):
+            return cls.model_key_plural
+        return '{}s'.format(cls.get_model_key())
+
+    @classmethod
+    def get_route_prefix(cls):
+        if hasattr(cls, 'route_prefix'):
+            return cls.route_prefix
+        return 'api.{}'.format(cls.get_model_key_plural())
+
+    @classmethod
+    def get_permission_prefix(cls):
+        if hasattr(cls, 'permission_prefix'):
+            return cls.permission_prefix
+        return cls.get_model_key_plural()
+
+    @classmethod
+    def get_url_prefix(cls):
+        if hasattr(cls, 'url_prefix'):
+            return cls.url_prefix
+        return '/api/{}'.format(cls.get_model_key_plural())
+
+    @property
+    def Session(self):
+        return Session
+
+    @api
+    def index(self):
+        objects = self.Session.query(self.model_class)
+
+        sort = self.request.params.get('sort')
+        if sort:
+            # TODO: this is fragile, but what to do if bad params?
+            sortkey, sortdir = sort.split('|')
+            sortkey = getattr(self.model_class, sortkey)
+            objects = objects.order_by(getattr(sortkey, sortdir)())
+
+        data = [self.normalize(obj) for obj in objects]
+        return data
+
+    def normalize(self, obj):
+        raise NotImplementedError("must implement `normalize()` method for: {}".format(self.__class__.__name__))
+
+    @classmethod
+    def defaults(cls, config):
+        route_prefix = cls.get_route_prefix()
+        url_prefix = cls.get_url_prefix()
+        permission_prefix = cls.get_permission_prefix()
+
+        # index
+        config.add_route(route_prefix, '{}/'.format(url_prefix), request_method='GET')
+        config.add_view(cls, attr='index', route_name=route_prefix,
+                        renderer='json', permission='{}.list'.format(permission_prefix))
diff --git a/tailbone/api/users.py b/tailbone/api/users.py
new file mode 100644
index 00000000..56cf17fd
--- /dev/null
+++ b/tailbone/api/users.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8; -*-
+################################################################################
+#
+#  Rattail -- Retail Software Framework
+#  Copyright © 2010-2018 Lance Edgar
+#
+#  This file is part of Rattail.
+#
+#  Rattail is free software: you can redistribute it and/or modify it under the
+#  terms of the GNU General Public License as published by the Free Software
+#  Foundation, either version 3 of the License, or (at your option) any later
+#  version.
+#
+#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
+#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+#  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+#  details.
+#
+#  You should have received a copy of the GNU General Public License along with
+#  Rattail.  If not, see .
+#
+################################################################################
+"""
+Tailbone Web API - User Views
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+import six
+
+from rattail.db import model
+
+from tailbone.api import APIMasterView
+
+
+class UserView(APIMasterView):
+
+    model_class = model.User
+
+    def normalize(self, user):
+        return {
+            'username': user.username,
+            'person': six.text_type(user.person or ''),
+        }
+
+
+def includeme(config):
+    UserView.defaults(config)