diff --git a/api/api.py b/api/api.py
index a9b707aa8f6db93ee1f1cb21599622ebbcfd2d25..74b1acba18085c5be4d1d2123cffb6742cd6ee57 100644
--- a/api/api.py
+++ b/api/api.py
@@ -9,7 +9,7 @@ import webapp2_extras.routes
 
 from . import base
 from .jobs.jobs import Job
-from .jobs.handlers import JobsHandler, JobHandler
+from .jobs.handlers import JobsHandler, JobHandler, GearsHandler, GearHandler
 from .dao.containerutil import FileReference, ContainerReference
 from . import encoder
 from . import root
@@ -210,6 +210,10 @@ routes = [
         webapp2.Route(r'/add',              JobsHandler, handler_method='add', methods=['POST']),
         webapp2.Route(r'/<:[^/]+>',         JobHandler,  name='job'),
     ]),
+    webapp2.Route(r'/api/gears',             GearsHandler),
+    webapp2_extras.routes.PathPrefixRoute(r'/api/gears', [
+        webapp2.Route(r'/<:[^/]+>',         GearHandler,  name='job'),
+    ]),
     webapp2.Route(r'/api/groups',                                   grouphandler.GroupHandler, handler_method='get_all', methods=['GET']),
     webapp2.Route(r'/api/groups',                                   grouphandler.GroupHandler, methods=['POST']),
     webapp2.Route(_format(r'/api/groups/<_id:{group_id_re}>'),      grouphandler.GroupHandler, name='group_details'),
diff --git a/api/jobs/gears.py b/api/jobs/gears.py
index 7c7f12f51cfde024bdf768ac594ce85d6931ab2b..e63e5fc6c6ff368c1fb9b9219c29af23e25fb724 100644
--- a/api/jobs/gears.py
+++ b/api/jobs/gears.py
@@ -6,27 +6,44 @@ from .. import config
 
 log = config.log
 
+# For now, gears are in a singleton, prefixed by a key
+SINGLETON_KEY = 'gear_list'
 
-def get_gears():
+def get_gears(fields=None):
     """
     Fetch the install-global gears from the database
     """
 
-    gear_doc  = config.db.singletons.find_one({'_id': 'gears'})
-    return gear_doc['gear_list']
+    projection = { }
+
+    if fields is None:
+        fields = [ ]
+        projection = { SINGLETON_KEY: 1 }
+    else:
+        fields.append('name')
+
+    query = {'_id': 'gears'}
+
+    for f in fields:
+        projection[SINGLETON_KEY + '.' + f] = 1
+
+    gear_doc = config.db.singletons.find_one(query, projection)
+
+    # print gear_doc
+    return gear_doc[SINGLETON_KEY]
 
 def get_gear_by_name(name):
 
     # Find a gear from the list by name
     gear_doc = config.db.singletons.find_one(
         {'_id': 'gears'},
-        {'gear_list': { '$elemMatch': {
+        {SINGLETON_KEY: { '$elemMatch': {
             'name': name
         }}
     })
 
-    if gear_doc is None:
+    if gear_doc is None or gear_doc.get(SINGLETON_KEY) is None:
         raise Exception('Unknown gear ' + name)
 
     # Mongo returns the full document: { '_id' : 'gears', 'gear_list' : [ { .. } ] }, so strip that out
-    return gear_doc['gear_list'][0]
+    return gear_doc[SINGLETON_KEY][0]
diff --git a/api/jobs/handlers.py b/api/jobs/handlers.py
index 1e371f7b9f113006e8a5c2ba96d48c9df5f8e306..01dd700aebac28ca83be5feb8226eec4a2dae6bd 100644
--- a/api/jobs/handlers.py
+++ b/api/jobs/handlers.py
@@ -7,12 +7,121 @@ from ..dao.containerutil import create_filereference_from_dictionary, create_con
 from .. import base
 from .. import config
 
+from .gears import get_gears, get_gear_by_name
 from .jobs import Job
 from .queue import Queue
 
 log = config.log
 
 
+
+class GearsHandler(base.RequestHandler):
+
+    """Provide /gears API routes."""
+
+    def get(self):
+        """
+        .. http:get:: /api/gears
+
+            List all gears.
+
+            :query fields: filter fields returned. Defaults to ['name']. Pass 'all' for everything.
+            :type fields: string
+
+            :statuscode 200: no error
+
+            **Example request**:
+
+            .. sourcecode:: http
+
+                GET /api/gears HTTP/1.1
+                Host: demo.flywheel.io
+                Accept: */*
+
+
+            **Example response**:
+
+            .. sourcecode:: http
+
+                HTTP/1.1 200 OK
+                Vary: Accept-Encoding
+                Content-Type: application/json; charset=utf-8
+                [
+                    {
+                        "name": "dicom_mr_classifier"
+                    },
+                    {
+                        "name": "dcm_convert"
+                    },
+                    {
+                        "name": "qa-report-fmri"
+                    }
+                ]
+        """
+
+        if self.public_request:
+            self.abort(403, 'Request requires login')
+
+        fields = self.request.GET.getall('fields')
+        if 'all' in fields:
+            fields = None
+
+        return get_gears(fields)
+
+
+class GearHandler(base.RequestHandler):
+
+    """Provide /gears/x API routes."""
+
+    def get(self, _id):
+        """
+        .. http:get:: /api/gears/(gid)
+
+            Detail a gear.
+
+            :statuscode 200: no error
+
+            **Example request**:
+
+            .. sourcecode:: http
+
+                GET /api/gears/dcm_convert HTTP/1.1
+                Host: demo.flywheel.io
+                Accept: */*
+
+
+            **Example response**:
+
+            .. sourcecode:: http
+
+                HTTP/1.1 200 OK
+                Vary: Accept-Encoding
+                Content-Type: application/json; charset=utf-8
+                {
+                    "name": "dcm_convert"
+                    "manifest": {
+                        "config": {},
+                        "inputs": {
+                            "dicom": {
+                                "base": "file",
+                                "type": {
+                                    "enum": [
+                                        "dicom"
+                                    ]
+                                }
+                            }
+                        },
+                    },
+                }
+
+        """
+
+        if self.public_request:
+            self.abort(403, 'Request requires login')
+
+        return get_gear_by_name(_id)
+
+
 class JobsHandler(base.RequestHandler):
 
     """Provide /jobs API routes."""