diff --git a/gracedb/api.py b/gracedb/api.py
index 286ef63177de4d689cf27c5ebb1b80d2760ea80e..d203986b0fb73363816c8ed846f6b636457b567a 100644
--- a/gracedb/api.py
+++ b/gracedb/api.py
@@ -1,26 +1,41 @@
 
 from django.http import HttpResponse, HttpResponseNotFound
+from django.core.urlresolvers import reverse
+
+import simplejson
 
 from gracedb.models import Event
 
 import os
 
-def download(request, graceid, filename=None):
-    #response = HttpResponse(buildVOEvent(event), content_type="application/xml")
-    if not filename:
-        response = HttpResponseNotFound("Not Implemented.")
-        response.status_code = 404
+def download(request, graceid, filename=""):
+    # Do not filename to be None.  That messes up later os.path.join
+    filename = filename or ""
+
     try:
         event = Event.getByGraceid(graceid)
-        filepath = os.path.join(event.datadir(), 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")
-        else:
-            response = HttpResponse(open(filepath, "r"), content_type="application/octet-stream")
-            response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(filename)
     except Event.DoesNotExist:
-        response = HttpResponseNotFound("Event does not exist")
+        return HttpResponseNotFound("Event not found")
+
+    filepath = os.path.join(event.datadir(), 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 not filename:
+        # Get list of files w/urls.
+        rv = {}
+        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(download, args=[graceid, filename])
+        response = HttpResponse(simplejson.dumps(rv), content_type="application/json")
+    else:
+        # get an actual file.
+        response = HttpResponse(open(filepath, "r"), content_type="application/octet-stream")
+        response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(filename)
 
     return response
diff --git a/gracedb/urls.py b/gracedb/urls.py
index 7a2046701d5ad3322e183e7ddb93590143cd486f..3505e42abd1ab3b461832aa1b58123d40051fdf4 100644
--- a/gracedb/urls.py
+++ b/gracedb/urls.py
@@ -3,6 +3,8 @@ from django.conf.urls.defaults import *
 
 #import django.views.generic.list_detail
 
+from gracedb.api import download
+
 urlpatterns = patterns('gracedb.views',
     url (r'^$', 'index', name="home"),
     url (r'^create/$', 'create', name="create"),
@@ -10,6 +12,8 @@ urlpatterns = patterns('gracedb.views',
     url (r'^view/(?P<graceid>[\w\d]+)', 'view', name="view"),
     url (r'^voevent/(?P<graceid>[\w\d]+)', 'voevent', name="voevent"),
     url (r'^skyalert/(?P<graceid>[\w\d]+)', 'skyalert', name="skyalert"),
+    url (r'^(?P<graceid>[\w\d]+)$', 'view', name="view2"),
+    url (r'^(?P<graceid>[\w\d]+)/files/(?P<filename>.+)$', download, name="file"),
 
 
 #   (r'^view/(?P<uid>[\w\d]+)', 'view'),
diff --git a/gracedb/views.py b/gracedb/views.py
index 99cfdf81249f0178a47d836ca8b9f2a834c4df23..3e840dd805e3498d883f25ec1907d1fb2506063c 100644
--- a/gracedb/views.py
+++ b/gracedb/views.py
@@ -1,6 +1,6 @@
 
 from django.http import HttpResponse
-from django.http import HttpResponseRedirect, HttpResponseNotFound, Http404
+from django.http import HttpResponseRedirect, HttpResponseNotFound, HttpResponseBadRequest, Http404
 from django.template import RequestContext
 from django.core.urlresolvers import reverse, get_script_prefix
 from django.shortcuts import render_to_response
@@ -504,7 +504,17 @@ def cli_search(request):
     if form.is_valid():
         query = form.cleaned_data['query']
         objects = Event.objects.filter(query).distinct()
-        # Assemble the output... should be able to choose format.
+
+        if 'ligolw' in request.POST or 'ligolw' in request.GET:
+            from glue.ligolw import utils
+            if objects.count() > 1000:
+                return HttpResponseBadRequest("Too many events.")
+            xmldoc = assembleLigoLw(objects)
+            response = HttpResponse(mimetype='application/xml')
+            response['Content-Disposition'] = 'attachment; filename=gracedb-query.xml'
+            utils.write_fileobj(xmldoc, response)
+            return response
+
         accessFun = {
             "labels" : lambda e: \
                 ",".join([labelling.label.name for labelling in e.labelling_set.all()]),
@@ -539,6 +549,25 @@ def cli_search(request):
     response.write(msg)
     return response
 
+
+def assembleLigoLw(objects):
+    from glue.ligolw import ligolw
+    # lsctables MUST be loaded before utils.
+    from glue.ligolw import lsctables
+    from glue.ligolw import utils
+    from glue.ligolw.utils import ligolw_add
+
+    xmldoc = ligolw.Document()
+    for obj in objects:
+        fname = os.path.join(GRACEDB_DATA_DIR, obj.graceid(), "private", "coinc.xml")
+        utils.load_filename(fname, xmldoc=xmldoc)
+
+    ligolw_add.reassign_ids(xmldoc)
+    ligolw_add.merge_ligolws(xmldoc)
+    ligolw_add.merge_compatible_tables(xmldoc)
+    return xmldoc
+
+
 def search(request, format=""):
     assert request.ligouser
     # XXX DO NOT HARDCODE THIS
@@ -602,25 +631,12 @@ def search(request, format=""):
                 return jqgridResponse(request, objects)
             elif 'ligolw' in request.POST or 'ligolw' in request.GET:
 
+                from glue.ligolw import utils
                 if objects.count() > 1000:
                     # XXX  Make this -- Better.
                     return HttpResponse("Sorry -- no more than 1000 events currently allowed.")
 
-                from glue.ligolw import ligolw
-                # lsctables MUST be loaded before utils.
-                from glue.ligolw import lsctables
-                from glue.ligolw import utils
-                from glue.ligolw.utils import ligolw_add
-                from settings import GRACEDB_DATA_DIR
-
-                xmldoc = ligolw.Document()
-                for obj in objects:
-                    fname = os.path.join(GRACEDB_DATA_DIR, obj.graceid(), "private", "coinc.xml")
-                    utils.load_filename(fname, xmldoc=xmldoc)
-
-                ligolw_add.reassign_ids(xmldoc)
-                ligolw_add.merge_ligolws(xmldoc)
-                ligolw_add.merge_compatible_tables(xmldoc)
+                xmldoc = assembleLigoLw(objects)
 
                 response = HttpResponse(mimetype='application/xml')
                 response['Content-Disposition'] = 'attachment; filename=gracedb-query.xml'