diff --git a/__init__.py b/__init__.py
index 28865f4b02b3b614d830ddfbb2e1132c9a4b06f0..e1e8b10697488c379dc643d1be9e177b23337379 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,3 +1,5 @@
 # @author:  Gunnar Schaefer
 
 import nimsapi
+
+app = nimsapi.app
diff --git a/epochs.py b/epochs.py
index 945e06fc794b973a92ec24f83e17a16e7dc01aaa..34629c898fd07d2b40238a8ac9a998953dd7241e 100644
--- a/epochs.py
+++ b/epochs.py
@@ -9,9 +9,43 @@ import nimsapiutil
 
 class Epochs(nimsapiutil.NIMSRequestHandler):
 
+    json_schema = {
+        '$schema': 'http://json-schema.org/draft-04/schema#',
+        'title': 'Epoch List',
+        'type': 'array',
+        'items': {
+            'title': 'Epoch',
+            'type': 'object',
+            'properties': {
+                '_id': {
+                    'title': 'Database ID',
+                },
+                'timestamp': {
+                    'title': 'Timestamp',
+                },
+                'datatype': {
+                    'title': 'Datatype',
+                    'type': 'string',
+                },
+                'series': {
+                    'title': 'Series',
+                    'type': 'integer',
+                },
+                'acquisition': {
+                    'title': 'Acquisition',
+                    'type': 'integer',
+                },
+                'description': {
+                    'title': 'Description',
+                    'type': 'string',
+                },
+            }
+        }
+    }
+
     def count(self, iid):
         """Return the number of Epochs."""
-        self.response.write('epochs count\n')
+        self.response.write(json.dumps(self.app.db.epochs.count()))
 
     def post(self, iid):
         """Create a new Epoch."""
@@ -39,6 +73,142 @@ class Epochs(nimsapiutil.NIMSRequestHandler):
 
 class Epoch(nimsapiutil.NIMSRequestHandler):
 
+    json_schema = {
+        '$schema': 'http://json-schema.org/draft-04/schema#',
+        'title': 'Epoch',
+        'type': 'object',
+        'properties': {
+            '_id': {
+                'title': 'Database ID',
+            },
+            'session': {
+                'title': 'Session ID',
+            },
+            'timestamp': {
+                'title': 'Timestamp',
+            },
+            'session_uid': {
+                'title': 'DICOM UID',
+                'type': 'string',
+            },
+            'datatype': {
+                'title': 'Datatype',
+                'type': 'string',
+            },
+            'series': {
+                'title': 'Series',
+                'type': 'integer',
+            },
+            'acquisition': {
+                'title': 'Acquisition',
+                'type': 'integer',
+            },
+            'description': {
+                'title': 'Description',
+                'type': 'string',
+                'maxLength': 64,
+            },
+            'protocol': {
+                'title': 'Protocol',
+                'type': 'string',
+                'maxLength': 64,
+            },
+            'rx_coil': {
+                'title': 'Coil',
+                'type': 'string',
+                'maxLength': 64,
+            },
+            'device': {
+                'title': 'Device',
+                'type': 'string',
+                'maxLength': 64,
+            },
+            'size': {
+                'title': 'Size',
+                'type': 'array',
+                'items': {
+                    'type': 'integer',
+                }
+            },
+            'acquisition_matrix': {
+                'title': 'Acquisition Matrix',
+                'type': 'array',
+                'items': {
+                    'type': 'number',
+                }
+            },
+            'fov': {
+                'title': 'Field of View',
+                'type': 'array',
+                'items': {
+                    'type': 'number',
+                }
+            },
+            'mm_per_voxel': {
+                'title': 'mm per Voxel',
+                'type': 'array',
+                'items': {
+                    'type': 'number',
+                }
+            },
+            'flip_angle': {
+                'title': 'Flip Angle',
+                'type': 'integer',
+            },
+            'num_averages': {
+                'title': 'Averages',
+                'type': 'integer',
+            },
+            'num_bands': {
+                'title': 'Bands',
+                'type': 'integer',
+            },
+            'num_echos': {
+                'title': 'Echos',
+                'type': 'integer',
+            },
+            'num_slices': {
+                'title': 'Slices',
+                'type': 'integer',
+            },
+            'num_timepoints': {
+                'title': 'Time Points',
+                'type': 'integer',
+            },
+            'pixel_bandwidth': {
+                'title': 'Pixel Bandwidth',
+                'type': 'number',
+            },
+            'prescribed_duration': {
+                'title': 'Prescribed Duration',
+                'type': 'number',
+            },
+            'duration': {
+                'title': 'Duration',
+                'type': 'number',
+            },
+            'slice_encode_undersample': {
+                'title': 'Slice Encode Undersample',
+                'type': 'integer',
+            },
+            'te': {
+                'title': 'Te',
+                'type': 'number',
+            },
+            'tr': {
+                'title': 'Tr',
+                'type': 'number',
+            },
+            'files': {
+                'title': 'Files',
+                'type': 'array',
+                'items': nimsapiutil.NIMSRequestHandler.file_schema,
+                'uniqueItems': True,
+            },
+        },
+        'required': ['_id'],
+    }
+
     def get(self, iid, eid):
         """Return one Epoch, conditionally with details."""
         epoch = self.app.db.epochs.find_one({'_id': bson.objectid.ObjectId(eid)})
diff --git a/experiments.py b/experiments.py
index 8c0acfd767577866b7a4fd4a06bf678738a198dd..e617c338c2ad24416204a91c4dcdbad53b9db279 100644
--- a/experiments.py
+++ b/experiments.py
@@ -9,9 +9,35 @@ import nimsapiutil
 
 class Experiments(nimsapiutil.NIMSRequestHandler):
 
+    json_schema = {
+        '$schema': 'http://json-schema.org/draft-04/schema#',
+        'title': 'Experiment List',
+        'type': 'array',
+        'items': {
+            'title': 'Experiment',
+            'type': 'object',
+            'properties': {
+                '_id': {
+                    'title': 'Database ID',
+                },
+                'firstname': {
+                    'title': 'First Name',
+                    'type': 'string',
+                },
+                'lastname': {
+                    'title': 'Last Name',
+                    'type': 'string',
+                },
+                'email_hash': {
+                    'type': 'string',
+                },
+            }
+        }
+    }
+
     def count(self, iid):
         """Return the number of Experiments."""
-        self.response.write('%d experiments\n' % self.app.db.experiments.count())
+        self.response.write(json.dumps(self.app.db.experiments.count()))
 
     def post(self, iid):
         """Create a new Experiment."""
@@ -38,6 +64,41 @@ class Experiments(nimsapiutil.NIMSRequestHandler):
 
 class Experiment(nimsapiutil.NIMSRequestHandler):
 
+    json_schema = {
+        '$schema': 'http://json-schema.org/draft-04/schema#',
+        'title': 'Experiment',
+        'type': 'object',
+        'properties': {
+            '_id': {
+                'title': 'Database ID',
+            },
+            'timestamp': {
+                'title': 'Timestamp',
+            },
+            'group': {
+                'title': 'Group',
+                'type': 'string',
+            },
+            'name': {
+                'title': 'Name',
+                'type': 'string',
+                'maxLength': 32,
+            },
+            'permissions': {
+                'title': 'Permissions',
+                'type': 'object',
+                'minProperties': 1,
+            },
+            'files': {
+                'title': 'Files',
+                'type': 'array',
+                'items': nimsapiutil.NIMSRequestHandler.file_schema,
+                'uniqueItems': True,
+            },
+        },
+        'required': ['_id', 'group', 'name'],
+    }
+
     def get(self, iid, xid):
         """Return one Experiment, conditionally with details."""
         experiment = self.app.db.experiments.find_one({'_id': bson.objectid.ObjectId(xid)})
diff --git a/nimsapi.py b/nimsapi.py
index 717f5f081a8cdc637c66de78b83dfdfebf0a9139..030afccd00346ddfeb7cb28512ef3888155a05a3 100755
--- a/nimsapi.py
+++ b/nimsapi.py
@@ -34,8 +34,44 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
 
     def get(self):
         """Return API documentation"""
+        resources = [
+                ('/experiments', 'list of experiments'),
+                ('/collections', 'list of collections'),
+                ('/experiments/<xid>/sessions', 'list of sessions for one experiment'),
+                ('/collections/<xid>/sessions', 'list of sessions for one collection'),
+                ('/sessions/<sid>/epochs', 'list of epochs for one session'),
+                ('', ''),
+                ('/experiments/<xid>', 'details for one experiment'),
+                ('/collections/<xid>', 'details for one collection'),
+                ('/sessions/<sid>', 'details for one session'),
+                ('/epochs/<eid>', 'details for one epoch'),
+                ('', ''),
+                ('/users', 'list of users'),
+                ('/groups', 'list of groups'),
+                ('/users/<uid>', 'details for one user'),
+                ('/groups/<gid>', 'details for one group'),
+        ]
         self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
-        self.response.write('nimsapi - {0}\n'.format(self.app.config['site_id']))
+        self.response.write('<html>\n')
+        self.response.write('<head>\n')
+        self.response.write('<title>NIMSAPI</title>\n')
+        self.response.write('<style type="text/css">\n')
+        self.response.write('.tftable {font-size:12px;color:#333333;width:100%;border-width: 1px;border-color: #a9a9a9;border-collapse: collapse;}\n')
+        self.response.write('.tftable th {font-size:12px;background-color:#b8b8b8;border-width: 1px;padding: 8px;border-style: solid;border-color: #a9a9a9;text-align:left;}\n')
+        self.response.write('.tftable tr {background-color:#cdcdcd;}\n')
+        self.response.write('.tftable td {font-size:12px;border-width: 1px;padding: 8px;border-style: solid;border-color: #a9a9a9;}\n')
+        self.response.write('</style>\n')
+        self.response.write('</head>\n')
+        self.response.write('<body>\n')
+        self.response.write('<p>nimsapi - ' + self.app.config['site_id'] + '</p>\n')
+        self.response.write('<table class="tftable" border="1">\n')
+        self.response.write('<tr><th>Resource</th><th>Description</th></tr>\n')
+        for r, d in resources:
+            r = r.replace('<', '&#60;').replace('>', '&#62;')
+            self.response.write('<tr><td>%s</td><td>%s</td></tr>\n' % (r, d))
+        self.response.write('</table>\n')
+        self.response.write('</body>\n')
+        self.response.write('</html>\n')
 
     def upload(self):
         # TODO add security: either authenticated user or machine-to-machine CRAM
@@ -46,7 +82,7 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
         with nimsutil.TempDir(prefix='.tmp', dir=stage_path) as tempdir_path:
             hash_ = hashlib.md5()
             upload_filepath = os.path.join(tempdir_path, filename)
-            log.info(os.path.basename(upload_filepath))
+            log.info('receiving upload ' + os.path.basename(upload_filepath))
             with open(upload_filepath, 'wb') as upload_file:
                 for chunk in iter(lambda: self.request.body_file.read(2**20), ''):
                     hash_.update(chunk)
@@ -55,7 +91,7 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
                 self.abort(400, 'Content-MD5 mismatch.')
             if not tarfile.is_tarfile(upload_filepath) and not zipfile.is_zipfile(upload_filepath):
                 self.abort(415)
-            os.rename(upload_filepath, os.path.join(stage_path, str(uuid.uuid1()) + '_' + fid)) # add UUID to prevent clobbering files
+            os.rename(upload_filepath, os.path.join(stage_path, str(uuid.uuid1()) + '_' + filename)) # add UUID to prevent clobbering files
 
     def download(self):
         paths = []
@@ -72,6 +108,42 @@ class NIMSAPI(nimsapiutil.NIMSRequestHandler):
 
 class Users(nimsapiutil.NIMSRequestHandler):
 
+    json_schema = {
+        '$schema': 'http://json-schema.org/draft-04/schema#',
+        'title': 'User List',
+        'type': 'array',
+        'items': {
+            'title': 'User',
+            'type': 'object',
+            'properties': {
+                '_id': {
+                    'title': 'Database ID',
+                    'type': 'string',
+                },
+                'firstname': {
+                    'title': 'First Name',
+                    'type': 'string',
+                    'default': '',
+                },
+                'lastname': {
+                    'title': 'Last Name',
+                    'type': 'string',
+                    'default': '',
+                },
+                'email': {
+                    'title': 'Email',
+                    'type': 'string',
+                    'format': 'email',
+                    'default': '',
+                },
+                'email_hash': {
+                    'type': 'string',
+                    'default': '',
+                },
+            }
+        }
+    }
+
     def count(self, iid):
         """Return the number of Users."""
         self.response.write('%d users\n' % self.app.db.users.count())
@@ -93,6 +165,43 @@ class Users(nimsapiutil.NIMSRequestHandler):
 
 class User(nimsapiutil.NIMSRequestHandler):
 
+    json_schema = {
+        '$schema': 'http://json-schema.org/draft-04/schema#',
+        'title': 'User',
+        'type': 'object',
+        'properties': {
+            '_id': {
+                'title': 'Database ID',
+                'type': 'string',
+            },
+            'firstname': {
+                'title': 'First Name',
+                'type': 'string',
+                'default': '',
+            },
+            'lastname': {
+                'title': 'Last Name',
+                'type': 'string',
+                'default': '',
+            },
+            'email': {
+                'title': 'Email',
+                'type': 'string',
+                'format': 'email',
+                'default': '',
+            },
+            'email_hash': {
+                'type': 'string',
+                'default': '',
+            },
+            'superuser': {
+                'title': 'Superuser',
+                'type': 'boolean',
+            },
+        },
+        'required': ['_id'],
+    }
+
     def get(self, iid, uid):
         """Return User details."""
         user = self.app.db.users.find_one({'_id': uid})
@@ -127,6 +236,22 @@ class User(nimsapiutil.NIMSRequestHandler):
 
 class Groups(nimsapiutil.NIMSRequestHandler):
 
+    json_schema = {
+        '$schema': 'http://json-schema.org/draft-04/schema#',
+        'title': 'Group List',
+        'type': 'array',
+        'items': {
+            'title': 'Group',
+            'type': 'object',
+            'properties': {
+                '_id': {
+                    'title': 'Database ID',
+                    'type': 'string',
+                },
+            }
+        }
+    }
+
     def count(self, iid):
         """Return the number of Groups."""
         self.response.write('%d groups\n' % self.app.db.groups.count())
@@ -148,6 +273,46 @@ class Groups(nimsapiutil.NIMSRequestHandler):
 
 class Group(nimsapiutil.NIMSRequestHandler):
 
+    json_schema = {
+        '$schema': 'http://json-schema.org/draft-04/schema#',
+        'title': 'Group',
+        'type': 'object',
+        'properties': {
+            '_id': {
+                'title': 'Database ID',
+                'type': 'string',
+            },
+            'pis': {
+                'title': 'PIs',
+                'type': 'array',
+                'default': [],
+                'items': {
+                    'type': 'string',
+                },
+                'uniqueItems': True,
+            },
+            'admins': {
+                'title': 'Admins',
+                'type': 'array',
+                'default': [],
+                'items': {
+                    'type': 'string',
+                },
+                'uniqueItems': True,
+            },
+            'memebers': {
+                'title': 'Members',
+                'type': 'array',
+                'default': [],
+                'items': {
+                    'type': 'string',
+                },
+                'uniqueItems': True,
+            },
+        },
+        'required': ['_id'],
+    }
+
     def get(self, iid, gid):
         """Return Group details."""
         group = self.app.db.groups.find_one({'_id': gid})
@@ -207,8 +372,8 @@ class ArgumentParser(argparse.ArgumentParser):
         super(ArgumentParser, self).__init__()
         self.add_argument('uri', help='NIMS DB URI')
         self.add_argument('stage_path', help='path to staging area')
-        self.add_argument('--pubkey', default='internims/NIMSpubkey.pub', help='path to ssl pubkey')
-        self.add_argument('-u', '--uid', default='local', help='site uid')
+        self.add_argument('-k', '--pubkey', help='path to public SSL key')
+        self.add_argument('-u', '--uid', default='local', help='site UID')
         self.add_argument('-f', '--logfile', help='path to log file')
         self.add_argument('-l', '--loglevel', default='info', help='path to log file')
         self.add_argument('-q', '--quiet', action='store_true', default=False, help='disable console logging')
@@ -218,36 +383,53 @@ routes = [
     webapp2_extras.routes.PathPrefixRoute(r'/nimsapi', [
         webapp2.Route(r'/download',                                 NIMSAPI, handler_method='download', methods=['GET']),
         webapp2.Route(r'/dump',                                     NIMSAPI, handler_method='dump', methods=['GET']),
-        webapp2.Route(r'/upload/<fid>',                             NIMSAPI, handler_method='upload', methods=['PUT']),
+        webapp2.Route(r'/upload',                                   NIMSAPI, handler_method='upload', methods=['PUT']),
         webapp2.Route(r'/remotes',                                  Remotes),
-        ]),
-    # webapp2_extras.routes.PathPrefixRoute has bug, variable MUST have regex
+    ]),
     webapp2_extras.routes.PathPrefixRoute(r'/nimsapi/<iid:[^/]+>', [
         webapp2.Route(r'/users',                                    Users),
         webapp2.Route(r'/users/count',                              Users, handler_method='count', methods=['GET']),
+        webapp2.Route(r'/users/listschema',                         Users, handler_method='schema', methods=['GET']),
+        webapp2.Route(r'/users/schema',                             User, handler_method='schema', methods=['GET']),
         webapp2.Route(r'/users/<uid>',                              User),
         webapp2.Route(r'/groups',                                   Groups),
         webapp2.Route(r'/groups/count',                             Groups, handler_method='count', methods=['GET']),
+        webapp2.Route(r'/groups/listschema',                        Groups, handler_method='schema', methods=['GET']),
+        webapp2.Route(r'/groups/schema',                            Group, handler_method='schema', methods=['GET']),
         webapp2.Route(r'/groups/<gid>',                             Group),
         webapp2.Route(r'/experiments',                              experiments.Experiments),
         webapp2.Route(r'/experiments/count',                        experiments.Experiments, handler_method='count', methods=['GET']),
+        webapp2.Route(r'/experiments/listschema',                   experiments.Experiments, handler_method='schema', methods=['GET']),
+        webapp2.Route(r'/experiments/schema',                       experiments.Experiment, handler_method='schema', methods=['GET']),
         webapp2.Route(r'/experiments/<xid:[0-9a-f]{24}>',           experiments.Experiment),
         webapp2.Route(r'/experiments/<xid:[0-9a-f]{24}>/sessions',  sessions.Sessions),
         webapp2.Route(r'/sessions/count',                           sessions.Sessions, handler_method='count', methods=['GET']),
+        webapp2.Route(r'/sessions/listschema',                      sessions.Sessions, handler_method='schema', methods=['GET']),
+        webapp2.Route(r'/sessions/schema',                          sessions.Session, handler_method='schema', methods=['GET']),
         webapp2.Route(r'/sessions/<sid:[0-9a-f]{24}>',              sessions.Session),
         webapp2.Route(r'/sessions/<sid:[0-9a-f]{24}>/move',         sessions.Session, handler_method='move'),
         webapp2.Route(r'/sessions/<sid:[0-9a-f]{24}>/epochs',       epochs.Epochs),
         webapp2.Route(r'/epochs/count',                             epochs.Epochs, handler_method='count', methods=['GET']),
+        webapp2.Route(r'/epochs/listschema',                        epochs.Epochs, handler_method='schema', methods=['GET']),
+        webapp2.Route(r'/epochs/schema',                            epochs.Epoch, handler_method='schema', methods=['GET']),
         webapp2.Route(r'/epochs/<eid:[0-9a-f]{24}>',                epochs.Epoch),
     ]),
 ]
 
+app = webapp2.WSGIApplication(routes, debug=True)
+
 
 if __name__ == '__main__':
     args = ArgumentParser().parse_args()
     nimsutil.configure_log(args.logfile, not args.quiet, args.loglevel)
 
     from paste import httpserver
-    app = webapp2.WSGIApplication(routes, debug=True, config=dict(stage_path=args.stage_path, site_id=args.uid, pubkey=args.pubkey))
+    app.config = dict(stage_path=args.stage_path, site_id=args.uid, pubkey=args.pubkey)
     app.db = (pymongo.MongoReplicaSetClient(args.uri) if 'replicaSet' in args.uri else pymongo.MongoClient(args.uri)).get_default_database()
     httpserver.serve(app, host=httpserver.socket.gethostname(), port='8080')
+
+# import nimsapi, webapp2, pymongo, bson.json_util
+# nimsapi.app.db = pymongo.MongoClient('mongodb://nims:cnimr750@slice.stanford.edu/nims').get_default_database()
+# response = webapp2.Request.blank('/nimsapi/local/users').get_response(nimsapi.app)
+# response.status
+# response.body
diff --git a/nimsapi.wsgi b/nimsapi.wsgi
index 7a83f7e4bf06cdde4962f753038394d9519ea68b..da8167645281ee17df0ec622978a3c87142a90da 100644
--- a/nimsapi.wsgi
+++ b/nimsapi.wsgi
@@ -17,11 +17,12 @@ import nimsapi
 import nimsutil
 
 logfile = '/var/local/log/nimsapi.log'
-db_uri = 'mongodb://nimsfs.stanford.edu,nimsbk.stanford.edu/nims?replicaSet=cni'
+db_uri = 'mongodb://nims:cnimr750@cnifs.stanford.edu,cnibk.stanford.edu/nims?replicaSet=cni'
 stage_path = '/scratch/upload'
 
 nimsutil.configure_log(logfile, False)
 db_client = pymongo.MongoReplicaSetClient(db_uri) if 'replicaSet' in db_uri else pymongo.MongoClient(db_uri)
 
-application = webapp2.WSGIApplication(nimsapi.nimsapi.routes, debug=False, config=dict(stage_path=stage_path))
+application = nimsapi.app
+application.config = dict(stage_path=stage_path))
 application.db = db_client.get_default_database()
diff --git a/nimsapiutil.py b/nimsapiutil.py
index 08d817be4766edbd25a068f25b2cbdcc8b362ace..231134761e95fa325ed45785d96b6a7b45827a0e 100644
--- a/nimsapiutil.py
+++ b/nimsapiutil.py
@@ -1,25 +1,62 @@
-# @author:  Gunnar Schaefer
-# @author:  Kevin S. Hahn
+# @author:  Gunnar Schaefer, Kevin S. Hahn
 
-import base64
-import datetime
 import json
+import base64
+import socket       # socket.gethostname()
 import logging
-import requests
 import webapp2
-import socket       # socket.gethostname()
+import datetime
+import requests
+import bson.json_util
 from Crypto.Hash import HMAC
 from Crypto.Random import random
-logging.basicConfig(level=logging.INFO)
+
+log = logging.getLogger('nimsapi')
 
 
 class NIMSRequestHandler(webapp2.RequestHandler):
 
     """fetches pubkey from own self.db.remotes. needs to be aware of OWN site uid"""
 
+    json_schema = None
+
+    file_schema = {
+        'title': 'File',
+        'type': 'object',
+        'properties': {
+            'datakind': {
+                'title': 'Data Kind',
+                'type': 'string',
+            },
+            'datatype': {
+                'title': 'Data Type',
+                'type': 'string',
+            },
+            'filetype': {
+                'title': 'File Type',
+                'type': 'string',
+            },
+            'filename': {
+                'title': 'File Name',
+                'type': 'string',
+            },
+            'ext': {
+                'title': 'File Name Extension',
+                'type': 'string',
+            },
+            'md5': {
+                'title': 'MD5',
+                'type': 'string',
+            },
+            'size': {
+                'title': 'Size',
+                'type': 'integer',
+            },
+        }
+    }
+
     def __init__(self, request=None, response=None):
-        webapp2.RequestHandler.__init__(self, request, response)
-        # call initialize, isntead of __init__
+        self.initialize(request, response)
         self.request.remote_user = self.request.get('user', None) # FIXME: auth system should set REMOTE_USER
         self.userid = self.request.remote_user or '@public'
         self.user = self.app.db.users.find_one({'_id': self.userid})
@@ -29,18 +66,17 @@ class NIMSRequestHandler(webapp2.RequestHandler):
             self.target_id = request.route_kwargs['iid']            # for what site is the request meant
         except KeyError:
             self.target_id = 'local'                                # change to site_id?
-        self.useragent = self.request.headers['user-agent']         # browser or internimsP2P request
         self.site_id = self.app.config['site_id']                   # what is THIS site
-        self.pubkey = open(self.app.config['pubkey']).read()
+        self.pubkey = open(self.app.config['pubkey']).read() if self.app.config['pubkey'] is not None else None
 
         # requests coming from another NIMS instance are dealt with differently
-        if self.useragent.startswith('NIMS Instance'):
-            logging.info("request from '{0}', interNIMS p2p initiated".format(self.useragent))
+        if self.request.user_agent.startswith('NIMS Instance'):
+            log.info("request from '{0}', interNIMS p2p initiated".format(self.request.user_agent))
             try:
                 authinfo = self.request.headers['authorization']
                 challenge_id, digest = base64.b64decode(authinfo).split()
                 user, remote_site = challenge_id.split(':')
-                # logging.info('{0} {1} {2}'.format(user, remote_site, digest))
+                # log.info('{0} {1} {2}'.format(user, remote_site, digest))
                 projection = {'_id': False, 'pubkey': True}
                 remote_pubkey = self.app.db.remotes.find_one({'_id': remote_site}, projection)['pubkey']
                 # get the challenge from db.challenges
@@ -52,7 +88,7 @@ class NIMSRequestHandler(webapp2.RequestHandler):
                 h = HMAC.new(remote_pubkey, challenge)
                 self.expected = base64.b64encode('%s %s' % (challenge_id, h.hexdigest()))
                 if self.expected == authinfo:
-                    logging.info('CRAM successful')
+                    log.info('CRAM successful')
                 else:
                     self.abort(403, 'Not Authorized: cram failed')
             except KeyError, e:
@@ -71,14 +107,14 @@ class NIMSRequestHandler(webapp2.RequestHandler):
     def dispatch(self):
         """dispatching and request forwarding"""
         if self.target_id in ['local', self.site_id]:
-            logging.info('{0} delegating to local {1}'.format(socket.gethostname(), self.request.url))
+            log.info('{0} delegating to local {1}'.format(socket.gethostname(), self.request.url))
             super(NIMSRequestHandler, self).dispatch()
         else:
-            logging.info('{0} delegating to remote {1}'.format(socket.gethostname(), self.target_id))
+            log.info('{0} delegating to remote {1}'.format(socket.gethostname(), self.target_id))
             # is target registered?
             target = self.app.db.remotes.find_one({'_id': self.target_id}, {'_id':False, 'hostname':True})
             if not target:
-                logging.info('remote host {0} not in auth list. DENIED'.format(self.target_id))
+                log.info('remote host {0} not in auth list. DENIED'.format(self.target_id))
                 self.abort(403, 'forbidden: site is not registered with interNIMS')
             self.cid = self.userid + ':' + self.site_id
             reqheaders = dict(self.request.headers)
@@ -94,12 +130,15 @@ class NIMSRequestHandler(webapp2.RequestHandler):
 
             if r.status_code == 401:
                 challenge = base64.b64decode(r.headers['www-authenticate'])
-                # logging.info('challenge {0} recieved'.format(challenge))
+                # log.info('challenge {0} recieved'.format(challenge))
                 h = HMAC.new(self.pubkey, challenge)
                 response = base64.b64encode('%s %s' % (self.cid, h.hexdigest()))
-                # logging.info('sending: {0} {1}'.format(self.cid, h.hexdigest()))
+                # log.info('sending: {0} {1}'.format(self.cid, h.hexdigest()))
                 #adjust the request and try again
                 reqheaders['authorization'] = response
                 r = requests.request(method=self.request.method, url=target_api, params=reqparams, data=self.request.body, headers=reqheaders, cookies=self.request.cookies)
 
             self.response.write(r.content)
+
+    def schema(self, *args, **kwargs):
+        self.response.write(json.dumps(self.json_schema, default=bson.json_util.default))
diff --git a/sessions.py b/sessions.py
index 3be07e3efc38f461c872931c080ca6b34a05a7d8..4b69170606bcffedb8f60fcd357bbc247ca5beda 100644
--- a/sessions.py
+++ b/sessions.py
@@ -9,9 +9,31 @@ import nimsapiutil
 
 class Sessions(nimsapiutil.NIMSRequestHandler):
 
+    json_schema = {
+        '$schema': 'http://json-schema.org/draft-04/schema#',
+        'title': 'Session List',
+        'type': 'array',
+        'items': {
+            'title': 'Session',
+            'type': 'object',
+            'properties': {
+                '_id': {
+                    'title': 'Database ID',
+                },
+                'timestamp': {
+                    'title': 'Timestamp',
+                },
+                'subject': {
+                    'title': 'Subject Code',
+                    'type': 'string',
+                },
+            }
+        }
+    }
+
     def count(self, iid):
         """Return the number of Sessions."""
-        self.response.write('sessions count\n')
+        self.response.write(json.dumps(self.app.db.sessions.count()))
 
     def post(self, iid):
         """Create a new Session"""
@@ -36,6 +58,54 @@ class Sessions(nimsapiutil.NIMSRequestHandler):
 
 class Session(nimsapiutil.NIMSRequestHandler):
 
+    json_schema = {
+        '$schema': 'http://json-schema.org/draft-04/schema#',
+        'title': 'Session',
+        'type': 'object',
+        'properties': {
+            '_id': {
+                'title': 'Database ID',
+            },
+            'experiment': {
+                'title': 'Experiment ID',
+            },
+            'timestamp': {
+                'title': 'Timestamp',
+            },
+            'uid': {
+                'title': 'DICOM UID',
+                'type': 'string',
+            },
+            'firstname': {
+                'title': 'First Name',
+                'type': 'string',
+            },
+            'lastname': {
+                'title': 'Last Name',
+                'type': 'string',
+            },
+            'patient_id': {
+                'title': 'Patient ID',
+                'type': 'string',
+            },
+            'subject': {
+                'title': 'Subject Code',
+                'type': 'string',
+            },
+            'exam': {
+                'title': 'Exam Number',
+                'type': 'integer',
+            },
+            'files': {
+                'title': 'Files',
+                'type': 'array',
+                'items': nimsapiutil.NIMSRequestHandler.file_schema,
+                'uniqueItems': True,
+            },
+        },
+        'required': ['_id', 'experiment', 'uid', 'patient_id', 'subject'],
+    }
+
     def get(self, iid, sid):
         """Return one Session, conditionally with details."""
         session = self.app.db.sessions.find_one({'_id': bson.objectid.ObjectId(sid)})