diff --git a/gracedb/api.py b/gracedb/api.py
index 7d81a0832f61540f6ae34e29bcaa1dee4886c227..68085471362f00b33c92dcc172bff7c059348575 100644
--- a/gracedb/api.py
+++ b/gracedb/api.py
@@ -1,12 +1,13 @@
 
 from django.http import HttpResponse, HttpResponseNotFound, HttpResponseForbidden, HttpResponseServerError
-from django.core.urlresolvers import reverse
+from django.core.urlresolvers import reverse as django_reverse
 
 import simplejson
 
 from gracedb.models import Event
 
 import os
+import urllib
 
 ##################################################################
 # Piston
@@ -14,21 +15,205 @@ import os
 
 ##################################################################
 # rest_framework
-from rest_framework import generics, serializers
+from rest_framework import serializers, status
+from rest_framework.response import Response
+#from rest_framework.renderers import JSONRenderer, JSONPRenderer, YAMLRenderer, XMLRenderer
+from forms import CreateEventForm
+from views import _createEventFromForm
+from rest_framework import parsers # YAMLParser, MultiPartParser
 
-class EventSerializer(serializers.ModelSerializer):
-    class Meta:
-        model = Event
+#from rest_framework.permissions import IsAuthenticated
+#from rest_framework.permissions import AllowAny
+from rest_framework import authentication
+from rest_framework.views import APIView
+from rest_framework.reverse import reverse
 
-class EventList(generics.ListCreateAPIView):
-    model = Event       
-    serializer_class = EventSerializer
+from django.contrib.auth.models import User as DjangoUser
 
-class EventDetail(generics.RetrieveUpdateDestroyAPIView):
-    model = Event   
-    serializer_class = EventSerializer
 
-def api_root(request):
+class LigoAuthentication(authentication.BaseAuthentication):
+    def authenticate(self, request):
+        try:
+            user = DjangoUser.objects.get(username=request.ligouser.unixid)
+        except DjangoUser.DoesNotExist:
+            user = None
+        return (user, None)
+
+
+class EventSerializer(serializers.Serializer):
+    group        = serializers.CharField(required=True, max_length=100)
+    analysisType = serializers.CharField(required=True, max_length=100)
+
+def eventToDict(event, columns=None, request=None):
+    """Convert an Event to a dictionary so it can be serialized.  (ugh)"""
+    # XXX Seems wrong.  Need to understand serializers.
+
+    rv = {}
+
+    rv['submitter'] = event.submitter.name
+    rv['created'] = event.created
+    rv['group'] = event.group.name
+    rv['graceid'] = event.graceid()
+    rv['analysisType'] = event.get_analysisType_display()
+    rv['instruments'] = event.instruments
+    rv['nevents'] = event.nevents
+    rv['far'] = event.far
+    rv['likelihood'] = event.likelihood
+    rv['labels'] = [labelling.label.name for labelling in event.labelling_set.all()]
+    rv['labels'] = [labelling.label.name for labelling in event.labelling_set.all()]
+    rv['links'] = {
+            "neighbors" : dict(
+                [(e.gpstime, reverse("event-detail", args=[e.graceid()]))
+                    for e in event.neighbors()]),
+            "data" : event.weburl(),
+            "log" : reverse("eventlog-list", args=[event.graceid()], request=request),
+            "files" : reverse("files", args=[event.graceid()], request=request),
+            "filemeta" : reverse("filemeta", args=[event.graceid()], request=request),
+            "self" : reverse("event-detail", args=[event.graceid()], request=request),
+            }
+    return rv
+
+class EventList(APIView):
+    """Docstring for *EventList* class!"""
+    #model = Event
+    #serializer_class = EventSerializer
+    ##renderer_classes = (JSONRenderer, JSONPRenderer, YAMLRenderer, XMLRenderer)
+    ##permission_classes = (AllowAny,)
+    ##authentication_classes = (authentication.SessionAuthentication,)
+    #authentication_classes = (LigoAuthentication,)
+    parser_classes = (parsers.MultiPartParser,)
+
+    def get(self, request):
+        """I am the GET docstring for EventList"""
+        query = request.QUERY_PARAMS.get("query")
+        limit = request.QUERY_PARAMS.get("limit", 10)
+        page = request.QUERY_PARAMS.get("page", 1)
+        orderby = request.QUERY_PARAMS.get("orderby", "-created")
+        if query is not None:
+            return Response("Query not implemented")
+        page = int(page)
+        limit = int(limit)
+        first = page*limit
+        events = Event.objects.order_by(orderby)
+        count = events.count()
+        last = max(0, (count / limit) - 1)
+        rv = {}
+        rv['events'] = [eventToDict(e, request=request)
+                for e in events[first:first+limit]]
+        baseuri = reverse('event-list', request=request)
+        d = { 'limit' : limit, 'page' : 0, "orderby": orderby }
+        rv['first'] = baseuri + "?" + urllib.urlencode(d)
+        d['page'] = last
+        rv['last'] = baseuri + "?" + urllib.urlencode(d)
+        rv['self'] = request.build_absolute_uri()
+        if page != last:
+            d['page'] = page+1
+            rv['next'] = baseuri + "?" + urllib.urlencode(d)
+        rv['total'] = count
+        return Response(rv)
+
+    def post(self, request, format=None):
+        rv = {}
+        form = CreateEventForm(request.POST, request.FILES)
+        # XXX Implement this please.
+        return Response("not yet")
+
+        if form.is_valid():
+            rv['valid'] = True
+            rv['efile'] = dir(request.FILES['eventFile'])
+            event, warnings = _createEventFromForm(request, form)
+            rv['warnings'] = warnings
+            rv['graceid'] = event.graceid()
+        else:
+            rv['valid'] = False
+            rv['error'] = ""
+            for key in form.errors:
+                # as_text() not str() otherwise we get HTML.
+                rv['error'] += "%s: %s\n" % (key, form.errors[key].as_text())
+        return Response(rv, status=status.HTTP_201_CREATED)
+
+class EventDetail(APIView):
+    parser_classes = (parsers.MultiPartParser,)
+    form = CreateEventForm
+
+    def get(self, request, graceid):
+        try:
+            event = Event.getByGraceid(graceid)
+        except Event.DoesNotExist:
+            # XXX Real error message.
+            return Response("blah blah blah", status=status.HTTP_404_NOT_FOUND)
+        return Response(eventToDict(event, request=request))
+
+    def put(self, request, graceid):
+        """ I am a doc.  Do I not get put anywhere? """
+        raise NotImplementedError()
+
+def eventLogToDict(log, n=None, request=None):
+    # XXX Messy.  n should not be here but in the model.
+    if n is None and request:
+        uri = request.build_absolute_uri()
+    elif n is not None and request:
+        uri = reverse("eventlog-detail", args=[log.event.graceid(), n], request=request)
+    else:
+        uri = ""
+    return [{
+                "comment" : log.comment,
+                "created" : log.created,
+                "issuer"  : log.issuer.name,
+                "self"    : uri,
+            }]
+
+class EventLogList(APIView):
+    def get(self, request, graceid):
+        try:
+            event = Event.getByGraceid(graceid)
+        except Event.DoesNotExist:
+            # XXX Real error message.
+            return Response("blah blah blah", status=status.HTTP_404_NOT_FOUND)
+        logset = event.eventlog_set
+        count = logset.count()
+        rv = [ eventLogToDict(log, n, request)
+                for (n, log) in zip(range(0,count+2), logset.iterator()) ]
+        return Response(rv)
+
+class EventLogDetail(APIView):
+    """docstring for EventLogDetail"""
+    def get(self, request, graceid, n):
+        try:
+            event = Event.getByGraceid(graceid)
+        except Event.DoesNotExist:
+            # XXX Real error message.
+            return Response("blah blah blah", status=status.HTTP_404_NOT_FOUND)
+        rv = event.eventlog_set.all()[int(n)]
+        return Response(eventLogToDict(rv, request=request))
+
+class GracedbRoot(APIView):
+    """Root of the Gracedb REST API"""
+    parser_classes = ()
+    def get(self, request):
+        # XXX scummy way to get a URI template.  Is there better?
+        detail = reverse("event-detail", args=["G1200"], request=request)
+        detail = detail.replace("G1200", "{graceid}")
+        log = reverse("eventlog-list", args=["G1200"], request=request)
+        log = log.replace("G1200", "{graceid}")
+
+        files = reverse("files", args=["G1200", "filename"], request=request)
+        files = files.replace("G1200", "{graceid}")
+        files = files.replace("filename", "{filename}")
+
+        filemeta = reverse('filemeta', args=["G1200", "filename"], request=request)
+        filemeta = filemeta.replace("G1200", "{graceid}")
+        filemeta = filemeta.replace("filename", "{filename}")
+
+        return Response({
+                "event-list" : reverse("event-list", request=request),
+                "event-detail-template" : detail,
+                "event-log-template" : log,
+                "files-template" : files,
+                "filemeta-template" : filemeta,
+               })
+
+def papi_root(request):
     """the api root"""
     return HttpResponse("""
 <html>
@@ -39,8 +224,8 @@ def api_root(request):
     </body>
 </html>
 """ % (
-    reverse('event-list'),
-    reverse('event-detail', args=[12]),
+    django_reverse('event-list'),
+    django_reverse('event-detail', args=["G12"]),
     ))
 #""" % reverse('download', kwargs={"graceid":"G12", "filename":"FLED_THE_FILER"}))
 #""" % reverse(download, args=["G12", "FRED_THE_FILE"]))
@@ -66,7 +251,7 @@ def download(request, graceid, filename=""):
     if filename.startswith("general/"):
         filename = filename[len("general/"):]
         general = True
-        
+
     filepath = os.path.join(event.datadir(general), filename)
 
     if not os.path.exists(filepath):
@@ -86,7 +271,7 @@ def download(request, graceid, filename=""):
             for filename in filenames:
                 # relative path from root of event data dir
                 filename = os.path.join(dirname, filename)
-                rv[filename] = reverse(download, args=[graceid, filename])
+                rv[filename] = django_reverse(download, args=[graceid, filename])
 
         # XXX UGH...  that awful general/ dir
         filepath = event.datadir(general=True)
@@ -96,7 +281,7 @@ def download(request, graceid, filename=""):
             for filename in filenames:
                 # relative path from root of event data dir
                 filename = os.path.join(dirname, filename)
-                rv[filename] = reverse(download, args=[graceid, filename])
+                rv[filename] = django_reverse(download, args=[graceid, filename])
 
         response = HttpResponse(simplejson.dumps(rv), content_type="application/json")
     elif os.path.isdir(filepath):
@@ -105,3 +290,76 @@ def download(request, graceid, filename=""):
         response = HttpResponseServerError("Should not happen.")
 
     return response
+
+
+class Files(APIView):
+    """Files Resource"""
+
+    def get(self, request, graceid, filename=""):
+        # Do not filename to be None.  That messes up later os.path.join
+        filename = filename or ""
+
+        try:
+            event = Event.getByGraceid(graceid)
+        except Event.DoesNotExist:
+            return HttpResponseNotFound("Event not found")
+
+        # The plan to deal with that wretched general/ directory maybe
+        # should be to move it INTO private.  Then externally, things
+        # would look like they do now, but the code here would be MUCH
+        # more sane and much shorter.
+
+        # UGLY hack to deal with /private vs /general dirs
+        general = False
+        if filename.startswith("general/"):
+            filename = filename[len("general/"):]
+            general = True
+
+        filepath = os.path.join(event.datadir(general), filename)
+
+        if not os.path.exists(filepath):
+            response = HttpResponseNotFound("File does not exist")
+        elif not os.access(filepath, os.R_OK):
+            response = HttpResponseNotFound("File not readable")
+        elif os.path.isfile(filepath):
+            # get an actual file.
+            response = HttpResponse(open(filepath, "r"), content_type="application/octet-stream")
+            response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(filename)
+        elif not filename:
+            # Get list of files w/urls.
+            rv = {}
+            filepath = event.datadir()
+            for dirname, dirnames, filenames in os.walk(filepath):
+                dirname = dirname[len(filepath):]  # cut off base event dir path
+                for filename in filenames:
+                    # relative path from root of event data dir
+                    filename = os.path.join(dirname, filename)
+                    rv[filename] = reverse("files", args=[graceid, filename], request=request)
+
+            # XXX UGH...  that awful general/ dir
+            # Actually not terrible, but do not like private/general as siblings.
+            # Their parent is basically empty.
+            filepath = event.datadir(general=True)
+            for dirname, dirnames, filenames in os.walk(filepath):
+                # XXX HORRIBLE
+                dirname = dirname[len(filepath)-len("general"):]  # cut off base event dir path
+                for filename in filenames:
+                    # relative path from root of event data dir
+                    filename = os.path.join(dirname, filename)
+                    rv[filename] = reverse("files", args=[graceid, filename], request=request)
+
+            #response = HttpResponse(simplejson.dumps(rv), content_type="application/json")
+            response = Response(rv)
+        elif os.path.isdir(filepath):
+            response = HttpResponseForbidden("%s is a directory" % filename)
+        else:
+            response = HttpResponseServerError("Should not happen.")
+
+        return response
+
+
+
+
+class FileMeta(APIView):
+    """File Metadata Resource"""
+    pass
diff --git a/gracedb/urls_rest.py b/gracedb/urls_rest.py
index 7c19bee4bcb108a9821227cf45f7ecb83ec54af1..9f1252f499a09fea2fd5215f5a754fc769dfcc47 100644
--- a/gracedb/urls_rest.py
+++ b/gracedb/urls_rest.py
@@ -2,17 +2,40 @@
 from django.conf.urls.defaults import patterns, url
 
 # rest_framework
+from gracedb.api import GracedbRoot
 from gracedb.api import EventList, EventDetail
+from gracedb.api import EventLogList, EventLogDetail
+from gracedb.api import Files, FileMeta
 
 urlpatterns = patterns('gracedb.api',
-    url (r'^$', 'api_root'),
-    # Piston
+    url (r'^$', GracedbRoot.as_view(), name="api-root"),
 
-    # rest_framework
-    url (r'^revents/$', EventList.as_view(), name='event-list'),
-    url (r'^revents/[GEHT](?P<pk>\d+)$', EventDetail.as_view(), name='event-detail'),
+    # Event Resources
+    # events/[{graceid}[/{version}]]
+    url (r'events/$', EventList.as_view(), name='event-list'),
+    url (r'events/(?P<graceid>[GEHT]\d+)$', EventDetail.as_view(), name='event-detail'),
+    # Event Log Resources
+    # events/{graceid}/logs/[{logid}]
+    url (r'events/(?P<graceid>[GEHT]\d+)/log/$', EventLogList.as_view(), name='eventlog-list'),
+    url (r'events/(?P<graceid>[GEHT]\d+)/log/(?P<n>\d+)$', EventLogDetail.as_view(), name='eventlog-detail'),
+
+    # Event File Resources
+    # events/{graceid}/files/[{filename}[/{version}]]
+    # XXX change to DjangoRF.  But this works now with the cli.
+    #url (r'^events/(?P<graceid>[\w\d]+)/files/(?P<filename>.+)?$', 'download', name="files"),
+    url (r'^events/(?P<graceid>[\w\d]+)/files/(?P<filename>.+)?$', Files.as_view(), name="files"),
+    # events/{graceid}/filemeta/[{filename}]
+    url (r'^events/(?P<graceid>[\w\d]+)/filemeta/(?P<filename>.+)?$', FileMeta.as_view(), name="filemeta"),
+
+    # Event Labels
+    # events/{graceid}/labels/[{label}]
+
+    # Event Slots
+    # events/{graceid}/slots/[{slotid}]
+
+    # Event Neighbors
+    # events/{graceid}/neighbors/[?neighborhood=N]
 
     # Legacy
-    url (r'^events/(?P<graceid>[\w\d]+)/files/(?P<filename>.+)?$', 'download', name="download"),
     url (r'^event/(?P<graceid>[\w\d]+)/files/(?P<filename>.+)?$', 'download', name="download2"),
 )