diff --git a/authority/server/db.js b/authority/server/db.js
index b00414b18c3c03529bae92a48df6f4282e1ebb9d..726d5c9dc2fef1ec11e57f02bdfd35065b4c1ba3 100644
--- a/authority/server/db.js
+++ b/authority/server/db.js
@@ -119,7 +119,6 @@ exports.addEmailToAccount = function(existing_email, email, pubkey, cb) {
 }
 
 exports.addKeyToEmail = function(existing_email, email, pubkey, cb) {
-  console.log("so you want to add a key ("+pubkey+") to " + email);
   emailToUserID(existing_email, function(userID) {
     if (userID == undefined) {
       cb("no such email: " + existing_email, undefined);
diff --git a/rp/server/httputils.js b/rp/server/httputils.js
new file mode 100644
index 0000000000000000000000000000000000000000..669cc049449a9958eee9958726404a01ea5fd0f8
--- /dev/null
+++ b/rp/server/httputils.js
@@ -0,0 +1,52 @@
+// various little utilities to make crafting boilerplate responses
+// simple
+
+exports.fourOhFour = function(resp, reason)
+{
+  resp.writeHead(404, {"Content-Type": "text/plain"});
+  resp.write("Not Found");
+  if (reason) {
+    resp.write(": " + reason);
+  }
+  resp.end();
+};
+
+exports.serverError = function(resp, reason)
+{
+  resp.writeHead(500, {"Content-Type": "text/plain"});
+  if (reason) resp.write(reason);
+  resp.end();
+};
+
+exports.badRequest = function(resp, reason)
+{
+  resp.writeHead(400, {"Content-Type": "text/plain"});
+  resp.write("Bad Request");
+  if (reason) {
+    resp.write(": " + reason);
+  }
+  resp.end();
+};
+
+exports.jsonResponse = function(resp, obj)
+{
+  resp.writeHead(200, {"Content-Type": "application/json"});
+  if (obj !== undefined) resp.write(JSON.stringify(obj));
+  resp.end();
+};
+
+exports.xmlResponse = function(resp, doc)
+{
+  resp.writeHead(200, {"Content-Type": "text/xml"});
+  if (doc !== undefined) resp.write(doc);
+  resp.end();
+};
+
+exports.checkGetArgs = function(req, args) {
+    [ "email", "pass", "pubkey" ].forEach(function(k) {
+      if (!urlobj.hasOwnProperty(k) || typeof urlobj[k] !== 'string') {
+        throw k;
+      }
+    });
+
+};
diff --git a/rp/server/run.js b/rp/server/run.js
new file mode 100644
index 0000000000000000000000000000000000000000..291f1e1cc25ee53b4182dcbad51aa7a222694d42
--- /dev/null
+++ b/rp/server/run.js
@@ -0,0 +1,45 @@
+const path = require('path'),
+       url = require('url'),
+     wsapi = require('./wsapi.js'),
+ httputils = require('./httputils.js'),
+   connect = require('connect'),
+        fs = require('fs');
+
+const STATIC_DIR = path.join(path.dirname(__dirname), "static");
+
+exports.handler = function(request, response, serveFile) {
+  // dispatch!
+  var urlpath = url.parse(request.url).pathname;
+
+  if (urlpath === '/authenticate') {
+    // XXX: do something
+    httputils.serverError(response, "notImplemented");
+  } else {
+    // node.js takes care of sanitizing the request path
+    // automatically serve index.html if this is a directory
+    var filename = path.join(STATIC_DIR, urlpath)
+    fs.stat(filename, function(err, s) {
+      if (err === null && s.isDirectory()) {
+        serveFile(path.join(filename, "index.html"), response);
+      } else {
+        serveFile(filename, response);
+      }
+    });
+  }
+};
+
+exports.setup = function(server) {
+  var week = (7 * 24 * 60 * 60 * 1000);
+
+  server
+    .use(connect.cookieParser())
+    .use(connect.session({
+      secret: "rhodesian ridgeback",
+      cookie: {
+        path: '/',
+        httpOnly: true,
+        expires:  new Date(Date.now() + week),// a week XXX: think about session security, etc
+        maxAge: week
+      }
+    }));
+}
diff --git a/rp/server/wsapi.js b/rp/server/wsapi.js
new file mode 100644
index 0000000000000000000000000000000000000000..e802e619dec3a7e094bab6e8b2398e92b1914b98
--- /dev/null
+++ b/rp/server/wsapi.js
@@ -0,0 +1,37 @@
+// a module which implements the authorities web server api.
+// every export is a function which is a WSAPI method handler
+
+const    url = require('url'),
+   httputils = require('./httputils.js')
+
+function checkParams(getArgs, resp, params) {
+  try {
+    params.forEach(function(k) {
+      if (!getArgs.hasOwnProperty(k) || typeof getArgs[k] !== 'string') {
+        throw k;
+      }
+    });
+  } catch(e) {
+    httputils.badRequest(resp, "missing '" + e + "' argument");
+    return false;
+  }
+  return true;
+}
+
+function isAuthed(req, resp) {
+  if (typeof req.session.authenticatedUser !== 'string') {
+    httputils.badRequest(resp, "requires authentication");
+    return false;
+  }
+  return true;
+}
+
+exports.all_words = function(req,resp) {
+  if (!isAuthed(req,resp)) return;
+  httputils.serverError(resp, "notImplemented");
+};
+
+exports.add_word = function(req,resp) {
+  if (!isAuthed(req,resp)) return;
+  httputils.serverError(resp, "notImplemented");
+};
diff --git a/rp/index.html b/rp/static/index.html
similarity index 100%
rename from rp/index.html
rename to rp/static/index.html
diff --git a/rp/jquery-min.js b/rp/static/jquery-min.js
similarity index 100%
rename from rp/jquery-min.js
rename to rp/static/jquery-min.js