diff --git a/application.py b/application.py
index 706c64a8c..26101f50b 100644
--- a/application.py
+++ b/application.py
@@ -6,12 +6,12 @@ from app import app as application
 logging.basicConfig(**application.config['LOGGING_CONFIG'])
 
 
-import endpoints.index
-import endpoints.api
-import endpoints.web
-import endpoints.tags
-import endpoints.registry
-import endpoints.webhooks
+from endpoints.api import api
+from endpoints.index import index
+from endpoints.web import web
+from endpoints.tags import tags
+from endpoints.registry import registry
+from endpoints.webhooks import webhooks
 
 
 logger = logging.getLogger(__name__)
@@ -20,6 +20,13 @@ if application.config.get('INCLUDE_TEST_ENDPOINTS', False):
   logger.debug('Loading test endpoints.')
   import endpoints.test
 
+application.register_blueprint(web)
+application.register_blueprint(index, url_prefix='/v1')
+application.register_blueprint(tags, url_prefix='/v1')
+application.register_blueprint(registry, url_prefix='/v1')
+application.register_blueprint(api, url_prefix='/api')
+application.register_blueprint(webhooks, url_prefix='/webhooks')
+
 # Remove this for prod config
 application.debug = True
 
diff --git a/endpoints/api.py b/endpoints/api.py
index 0cad539b6..050ca26f7 100644
--- a/endpoints/api.py
+++ b/endpoints/api.py
@@ -4,7 +4,7 @@ import requests
 import urlparse
 import json
 
-from flask import request, make_response, jsonify, abort, url_for
+from flask import request, make_response, jsonify, abort, url_for, Blueprint, session
 from flask.ext.login import current_user, logout_user
 from flask.ext.principal import identity_changed, AnonymousIdentity
 from functools import wraps
@@ -29,13 +29,25 @@ from endpoints.common import common_login
 from util.cache import cache_control
 from datetime import datetime, timedelta
 
-
 store = app.config['STORAGE']
 user_files = app.config['USERFILES']
 logger = logging.getLogger(__name__)
 
 route_data = None
 
+api = Blueprint('api', __name__)
+
+@api.before_request
+def csrf_protect():
+  if request.method != "GET" and request.method != "HEAD":
+    token = session.get('_csrf_token', None)
+    found_token = request.values.get('_csrf_token', None)
+
+    # TODO: add if not token here, once we are sure all sessions have a token.
+    if token != found_token:
+      abort(403)
+
+
 def get_route_data():
   global route_data
   if route_data:
@@ -43,14 +55,14 @@ def get_route_data():
 
   routes = []
   for rule in app.url_map.iter_rules():
-    if rule.rule.startswith('/api/'):
-      endpoint_method = globals()[rule.endpoint]
+    if rule.endpoint.startswith('api.'):
+      endpoint_method = globals()[rule.endpoint[4:]] # Remove api.
       is_internal = '__internal_call' in dir(endpoint_method)
       is_org_api = '__user_call' in dir(endpoint_method)
       methods = list(rule.methods.difference(['HEAD', 'OPTIONS']))
 
       route = {
-        'name': rule.endpoint,
+        'name': rule.endpoint[4:],
         'methods': methods,
         'path': rule.rule,
         'parameters': list(rule.arguments)
@@ -111,28 +123,19 @@ def org_api_call(user_call_name):
 
   return internal_decorator
 
-@app.errorhandler(model.DataModelException)
-def handle_dme(ex):
-  return make_response(ex.message, 400)
 
-
-@app.errorhandler(KeyError)
-def handle_dme_key_error(ex):
-  return make_response(ex.message, 400)
-
-
-@app.route('/api/discovery')
+@api.route('/discovery')
 def discovery():
   return jsonify(get_route_data())
 
   
-@app.route('/api/')
+@api.route('/')
 @internal_api_call
 def welcome():
   return make_response('welcome', 200)
 
 
-@app.route('/api/plans/')
+@api.route('/plans/')
 def list_plans():
   return jsonify({
     'plans': PLANS,
@@ -175,7 +178,7 @@ def user_view(user):
   }
 
 
-@app.route('/api/user/', methods=['GET'])
+@api.route('/user/', methods=['GET'])
 @internal_api_call
 def get_logged_in_user():
   if current_user.is_anonymous():
@@ -188,7 +191,7 @@ def get_logged_in_user():
   return jsonify(user_view(user))
 
 
-@app.route('/api/user/private', methods=['GET'])
+@api.route('/user/private', methods=['GET'])
 @api_login_required
 @internal_api_call
 def get_user_private_count():
@@ -209,7 +212,7 @@ def get_user_private_count():
   })
 
 
-@app.route('/api/user/convert', methods=['POST'])
+@api.route('/user/convert', methods=['POST'])
 @api_login_required
 @internal_api_call
 def convert_user_to_organization():
@@ -246,7 +249,7 @@ def convert_user_to_organization():
   return conduct_signin(admin_username, admin_password)
   
 
-@app.route('/api/user/', methods=['PUT'])
+@api.route('/user/', methods=['PUT'])
 @api_login_required
 @internal_api_call
 def change_user_details():
@@ -288,7 +291,7 @@ def change_user_details():
   return jsonify(user_view(user))
 
 
-@app.route('/api/user/', methods=['POST'])
+@api.route('/user/', methods=['POST'])
 @internal_api_call
 def create_new_user():
   user_data = request.get_json()
@@ -315,7 +318,7 @@ def create_new_user():
     return error_resp
 
 
-@app.route('/api/signin', methods=['POST'])
+@api.route('/signin', methods=['POST'])
 @internal_api_call
 def signin_user():
   signin_data = request.get_json()
@@ -348,7 +351,7 @@ def conduct_signin(username_or_email, password):
   return response
 
 
-@app.route("/api/signout", methods=['POST'])
+@api.route("/signout", methods=['POST'])
 @api_login_required
 @internal_api_call
 def logout():
@@ -357,7 +360,7 @@ def logout():
   return make_response('Success', 200)
 
 
-@app.route("/api/recovery", methods=['POST'])
+@api.route("/recovery", methods=['POST'])
 @internal_api_call
 def request_recovery_email():
   email = request.get_json()['email']
@@ -366,7 +369,7 @@ def request_recovery_email():
   return make_response('Created', 201)
 
 
-@app.route('/api/users/<prefix>', methods=['GET'])
+@api.route('/users/<prefix>', methods=['GET'])
 @api_login_required
 def get_matching_users(prefix):
   users = model.get_matching_users(prefix)
@@ -376,7 +379,7 @@ def get_matching_users(prefix):
   })
 
 
-@app.route('/api/entities/<prefix>', methods=['GET'])
+@api.route('/entities/<prefix>', methods=['GET'])
 @api_login_required
 def get_matching_entities(prefix):
   teams = []
@@ -442,7 +445,7 @@ def team_view(orgname, team):
   }
 
 
-@app.route('/api/organization/', methods=['POST'])
+@api.route('/organization/', methods=['POST'])
 @api_login_required
 @internal_api_call
 def create_organization():
@@ -491,7 +494,7 @@ def org_view(o, teams):
   return view
 
 
-@app.route('/api/organization/<orgname>', methods=['GET'])
+@api.route('/organization/<orgname>', methods=['GET'])
 @api_login_required
 def get_organization(orgname):
   permission = OrganizationMemberPermission(orgname)
@@ -507,7 +510,7 @@ def get_organization(orgname):
   abort(403)
 
 
-@app.route('/api/organization/<orgname>', methods=['PUT'])
+@api.route('/organization/<orgname>', methods=['PUT'])
 @api_login_required
 @org_api_call('change_user_details')
 def change_organization_details(orgname):
@@ -701,7 +704,7 @@ def update_organization_prototype_permission(orgname, prototypeid):
   abort(403)
 
 
-@app.route('/api/organization/<orgname>/members', methods=['GET'])
+@api.route('/organization/<orgname>/members', methods=['GET'])
 @api_login_required
 def get_organization_members(orgname):
   permission = AdministerOrganizationPermission(orgname)
@@ -730,7 +733,7 @@ def get_organization_members(orgname):
   abort(403)
 
 
-@app.route('/api/organization/<orgname>/members/<membername>', methods=['GET'])
+@api.route('/organization/<orgname>/members/<membername>', methods=['GET'])
 @api_login_required
 def get_organization_member(orgname, membername):
   permission = AdministerOrganizationPermission(orgname)
@@ -759,7 +762,7 @@ def get_organization_member(orgname, membername):
   abort(403)
 
 
-@app.route('/api/organization/<orgname>/private', methods=['GET'])
+@api.route('/organization/<orgname>/private', methods=['GET'])
 @api_login_required
 @internal_api_call
 def get_organization_private_allowed(orgname):
@@ -795,7 +798,7 @@ def member_view(member):
   }
 
 
-@app.route('/api/organization/<orgname>/team/<teamname>',
+@api.route('/organization/<orgname>/team/<teamname>',
            methods=['PUT', 'POST'])
 @api_login_required
 def update_organization_team(orgname, teamname):
@@ -841,7 +844,7 @@ def update_organization_team(orgname, teamname):
   abort(403)
 
 
-@app.route('/api/organization/<orgname>/team/<teamname>',
+@api.route('/organization/<orgname>/team/<teamname>',
            methods=['DELETE'])
 @api_login_required
 def delete_organization_team(orgname, teamname):
@@ -854,7 +857,7 @@ def delete_organization_team(orgname, teamname):
   abort(403)
 
 
-@app.route('/api/organization/<orgname>/team/<teamname>/members',
+@api.route('/organization/<orgname>/team/<teamname>/members',
            methods=['GET'])
 @api_login_required
 def get_organization_team_members(orgname, teamname):
@@ -877,7 +880,7 @@ def get_organization_team_members(orgname, teamname):
   abort(403)
 
 
-@app.route('/api/organization/<orgname>/team/<teamname>/members/<membername>',
+@api.route('/organization/<orgname>/team/<teamname>/members/<membername>',
            methods=['PUT', 'POST'])
 @api_login_required
 def update_organization_team_member(orgname, teamname, membername):
@@ -906,7 +909,7 @@ def update_organization_team_member(orgname, teamname, membername):
   abort(403)
 
 
-@app.route('/api/organization/<orgname>/team/<teamname>/members/<membername>',
+@api.route('/organization/<orgname>/team/<teamname>/members/<membername>',
            methods=['DELETE'])
 @api_login_required
 def delete_organization_team_member(orgname, teamname, membername):
@@ -922,7 +925,7 @@ def delete_organization_team_member(orgname, teamname, membername):
   abort(403)
 
 
-@app.route('/api/repository', methods=['POST'])
+@api.route('/repository', methods=['POST'])
 @api_login_required
 def create_repo():
   owner = current_user.db_user()
@@ -956,7 +959,7 @@ def create_repo():
   abort(403)
 
 
-@app.route('/api/find/repository', methods=['GET'])
+@api.route('/find/repository', methods=['GET'])
 def find_repos():
   prefix = request.args.get('query', '')
 
@@ -979,7 +982,7 @@ def find_repos():
   return jsonify(response)
 
 
-@app.route('/api/repository/', methods=['GET'])
+@api.route('/repository/', methods=['GET'])
 def list_repos():
   def repo_view(repo_obj):
     return {
@@ -1039,7 +1042,7 @@ def list_repos():
   return jsonify(response)
 
 
-@app.route('/api/repository/<path:repository>', methods=['PUT'])
+@api.route('/repository/<path:repository>', methods=['PUT'])
 @api_login_required
 @parse_repository_name
 def update_repo(namespace, repository):
@@ -1061,7 +1064,7 @@ def update_repo(namespace, repository):
   abort(403)
 
 
-@app.route('/api/repository/<path:repository>/changevisibility',
+@api.route('/repository/<path:repository>/changevisibility',
            methods=['POST'])
 @api_login_required
 @parse_repository_name
@@ -1082,7 +1085,7 @@ def change_repo_visibility(namespace, repository):
   abort(403)
 
 
-@app.route('/api/repository/<path:repository>', methods=['DELETE'])
+@api.route('/repository/<path:repository>', methods=['DELETE'])
 @api_login_required
 @parse_repository_name
 def delete_repository(namespace, repository):
@@ -1108,7 +1111,7 @@ def image_view(image):
   }
 
 
-@app.route('/api/repository/<path:repository>', methods=['GET'])
+@api.route('/repository/<path:repository>', methods=['GET'])
 @parse_repository_name
 def get_repo(namespace, repository):
   logger.debug('Get repo: %s/%s' % (namespace, repository))
@@ -1157,7 +1160,7 @@ def get_repo(namespace, repository):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/build/', methods=['GET'])
+@api.route('/repository/<path:repository>/build/', methods=['GET'])
 @api_login_required
 @parse_repository_name
 def get_repo_builds(namespace, repository):
@@ -1190,7 +1193,7 @@ def get_repo_builds(namespace, repository):
   abort(403)  # Permissions denied
 
 
-@app.route('/api/repository/<path:repository>/build/', methods=['POST'])
+@api.route('/repository/<path:repository>/build/', methods=['POST'])
 @api_login_required
 @parse_repository_name
 def request_repo_build(namespace, repository):
@@ -1228,7 +1231,7 @@ def webhook_view(webhook):
   }
 
 
-@app.route('/api/repository/<path:repository>/webhook/', methods=['POST'])
+@api.route('/repository/<path:repository>/webhook/', methods=['POST'])
 @api_login_required
 @parse_repository_name
 def create_webhook(namespace, repository):
@@ -1248,7 +1251,7 @@ def create_webhook(namespace, repository):
   abort(403)  # Permissions denied
 
 
-@app.route('/api/repository/<path:repository>/webhook/<public_id>',
+@api.route('/repository/<path:repository>/webhook/<public_id>',
            methods=['GET'])
 @api_login_required
 @parse_repository_name
@@ -1261,7 +1264,7 @@ def get_webhook(namespace, repository, public_id):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/webhook/', methods=['GET'])
+@api.route('/repository/<path:repository>/webhook/', methods=['GET'])
 @api_login_required
 @parse_repository_name
 def list_webhooks(namespace, repository):
@@ -1275,7 +1278,7 @@ def list_webhooks(namespace, repository):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/webhook/<public_id>',
+@api.route('/repository/<path:repository>/webhook/<public_id>',
            methods=['DELETE'])
 @api_login_required
 @parse_repository_name
@@ -1291,7 +1294,7 @@ def delete_webhook(namespace, repository, public_id):
   abort(403)  # Permission denied
 
 
-@app.route('/api/filedrop/', methods=['POST'])
+@api.route('/filedrop/', methods=['POST'])
 @api_login_required
 @internal_api_call
 def get_filedrop_url():
@@ -1318,7 +1321,7 @@ def wrap_role_view_org(role_json, user, org_members):
   return role_json
 
 
-@app.route('/api/repository/<path:repository>/image/', methods=['GET'])
+@api.route('/repository/<path:repository>/image/', methods=['GET'])
 @parse_repository_name
 def list_repository_images(namespace, repository):
   permission = ReadRepositoryPermission(namespace, repository)
@@ -1343,7 +1346,7 @@ def list_repository_images(namespace, repository):
   abort(403)
 
 
-@app.route('/api/repository/<path:repository>/image/<image_id>',
+@api.route('/repository/<path:repository>/image/<image_id>',
            methods=['GET'])
 @parse_repository_name
 def get_image(namespace, repository, image_id):
@@ -1357,7 +1360,7 @@ def get_image(namespace, repository, image_id):
   abort(403)
 
 
-@app.route('/api/repository/<path:repository>/image/<image_id>/changes',
+@api.route('/repository/<path:repository>/image/<image_id>/changes',
            methods=['GET'])
 @cache_control(max_age=60*60)  # Cache for one hour
 @parse_repository_name
@@ -1375,7 +1378,7 @@ def get_image_changes(namespace, repository, image_id):
   abort(403)
 
 
-@app.route('/api/repository/<path:repository>/tag/<tag>',
+@api.route('/api/repository/<path:repository>/tag/<tag>',
            methods=['DELETE'])
 @parse_repository_name
 def delete_full_tag(namespace, repository, tag):
@@ -1393,7 +1396,7 @@ def delete_full_tag(namespace, repository, tag):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/tag/<tag>/images',
+@api.route('/repository/<path:repository>/tag/<tag>/images',
            methods=['GET'])
 @parse_repository_name
 def list_tag_images(namespace, repository, tag):
@@ -1417,7 +1420,7 @@ def list_tag_images(namespace, repository, tag):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/permissions/team/',
+@api.route('/repository/<path:repository>/permissions/team/',
            methods=['GET'])
 @api_login_required
 @parse_repository_name
@@ -1434,7 +1437,7 @@ def list_repo_team_permissions(namespace, repository):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/permissions/user/',
+@api.route('/repository/<path:repository>/permissions/user/',
            methods=['GET'])
 @api_login_required
 @parse_repository_name
@@ -1475,7 +1478,7 @@ def list_repo_user_permissions(namespace, repository):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/permissions/user/<username>',
+@api.route('/repository/<path:repository>/permissions/user/<username>',
            methods=['GET'])
 @api_login_required
 @parse_repository_name
@@ -1500,7 +1503,7 @@ def get_user_permissions(namespace, repository, username):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/permissions/team/<teamname>',
+@api.route('/repository/<path:repository>/permissions/team/<teamname>',
            methods=['GET'])
 @api_login_required
 @parse_repository_name
@@ -1515,7 +1518,7 @@ def get_team_permissions(namespace, repository, teamname):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/permissions/user/<username>',
+@api.route('/repository/<path:repository>/permissions/user/<username>',
            methods=['PUT', 'POST'])
 @api_login_required
 @parse_repository_name
@@ -1558,7 +1561,7 @@ def change_user_permissions(namespace, repository, username):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/permissions/team/<teamname>',
+@api.route('/repository/<path:repository>/permissions/team/<teamname>',
            methods=['PUT', 'POST'])
 @api_login_required
 @parse_repository_name
@@ -1586,7 +1589,7 @@ def change_team_permissions(namespace, repository, teamname):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/permissions/user/<username>',
+@api.route('/repository/<path:repository>/permissions/user/<username>',
            methods=['DELETE'])
 @api_login_required
 @parse_repository_name
@@ -1611,7 +1614,7 @@ def delete_user_permissions(namespace, repository, username):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/permissions/team/<teamname>',
+@api.route('/repository/<path:repository>/permissions/team/<teamname>',
            methods=['DELETE'])
 @api_login_required
 @parse_repository_name
@@ -1637,7 +1640,7 @@ def token_view(token_obj):
   }
 
 
-@app.route('/api/repository/<path:repository>/tokens/', methods=['GET'])
+@api.route('/repository/<path:repository>/tokens/', methods=['GET'])
 @api_login_required
 @parse_repository_name
 def list_repo_tokens(namespace, repository):
@@ -1652,7 +1655,7 @@ def list_repo_tokens(namespace, repository):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/tokens/<code>', methods=['GET'])
+@api.route('/repository/<path:repository>/tokens/<code>', methods=['GET'])
 @api_login_required
 @parse_repository_name
 def get_tokens(namespace, repository, code):
@@ -1664,7 +1667,7 @@ def get_tokens(namespace, repository, code):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/tokens/', methods=['POST'])
+@api.route('/repository/<path:repository>/tokens/', methods=['POST'])
 @api_login_required
 @parse_repository_name
 def create_token(namespace, repository):
@@ -1686,7 +1689,7 @@ def create_token(namespace, repository):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/tokens/<code>', methods=['PUT'])
+@api.route('/repository/<path:repository>/tokens/<code>', methods=['PUT'])
 @api_login_required
 @parse_repository_name
 def change_token(namespace, repository, code):
@@ -1711,7 +1714,7 @@ def change_token(namespace, repository, code):
   abort(403)  # Permission denied
 
 
-@app.route('/api/repository/<path:repository>/tokens/<code>',
+@api.route('/repository/<path:repository>/tokens/<code>',
            methods=['DELETE'])
 @api_login_required
 @parse_repository_name
@@ -1739,7 +1742,7 @@ def subscription_view(stripe_subscription, used_repos):
   }
 
 
-@app.route('/api/user/card', methods=['GET'])
+@api.route('/user/card', methods=['GET'])
 @api_login_required
 @internal_api_call
 def get_user_card():
@@ -1747,7 +1750,7 @@ def get_user_card():
   return get_card(user)
 
 
-@app.route('/api/organization/<orgname>/card', methods=['GET'])
+@api.route('/organization/<orgname>/card', methods=['GET'])
 @api_login_required
 @internal_api_call
 @org_api_call('get_user_card')
@@ -1760,7 +1763,7 @@ def get_org_card(orgname):
   abort(403)
 
 
-@app.route('/api/user/card', methods=['POST'])
+@api.route('/user/card', methods=['POST'])
 @api_login_required
 @internal_api_call
 def set_user_card():
@@ -1771,7 +1774,7 @@ def set_user_card():
   return response
 
 
-@app.route('/api/organization/<orgname>/card', methods=['POST'])
+@api.route('/organization/<orgname>/card', methods=['POST'])
 @api_login_required
 @org_api_call('set_user_card')
 def set_org_card(orgname):
@@ -1823,7 +1826,7 @@ def get_card(user):
 
   return jsonify({'card': card_info})
 
-@app.route('/api/user/plan', methods=['PUT'])
+@api.route('/user/plan', methods=['PUT'])
 @api_login_required
 @internal_api_call
 def update_user_subscription():
@@ -1916,7 +1919,7 @@ def subscribe(user, plan, token, require_business_plan):
   return resp
 
 
-@app.route('/api/user/invoices', methods=['GET'])
+@api.route('/user/invoices', methods=['GET'])
 @api_login_required
 def list_user_invoices():
   user = current_user.db_user()
@@ -1926,7 +1929,7 @@ def list_user_invoices():
   return get_invoices(user.stripe_id)
 
 
-@app.route('/api/organization/<orgname>/invoices', methods=['GET'])
+@api.route('/organization/<orgname>/invoices', methods=['GET'])
 @api_login_required
 @org_api_call('list_user_invoices')
 def list_org_invoices(orgname):
@@ -1963,7 +1966,7 @@ def get_invoices(customer_id):
   })
 
 
-@app.route('/api/organization/<orgname>/plan', methods=['PUT'])
+@api.route('/organization/<orgname>/plan', methods=['PUT'])
 @api_login_required
 @internal_api_call
 @org_api_call('update_user_subscription')
@@ -1979,7 +1982,7 @@ def update_org_subscription(orgname):
   abort(403)
   
 
-@app.route('/api/user/plan', methods=['GET'])
+@api.route('/user/plan', methods=['GET'])
 @api_login_required
 @internal_api_call
 def get_user_subscription():
@@ -1998,7 +2001,7 @@ def get_user_subscription():
   })
 
 
-@app.route('/api/organization/<orgname>/plan', methods=['GET'])
+@api.route('/organization/<orgname>/plan', methods=['GET'])
 @api_login_required
 @internal_api_call
 @org_api_call('get_user_subscription')
@@ -2028,7 +2031,7 @@ def robot_view(name, token):
   }
 
 
-@app.route('/api/user/robots', methods=['GET'])
+@api.route('/user/robots', methods=['GET'])
 @api_login_required
 def get_user_robots():
   user = current_user.db_user()
@@ -2038,7 +2041,7 @@ def get_user_robots():
   })
 
 
-@app.route('/api/organization/<orgname>/robots', methods=['GET'])
+@api.route('/organization/<orgname>/robots', methods=['GET'])
 @api_login_required
 @org_api_call('get_user_robots')
 def get_org_robots(orgname):
@@ -2052,7 +2055,7 @@ def get_org_robots(orgname):
   abort(403)
 
 
-@app.route('/api/user/robots/<robot_shortname>', methods=['PUT'])
+@api.route('/user/robots/<robot_shortname>', methods=['PUT'])
 @api_login_required
 def create_user_robot(robot_shortname):
   parent = current_user.db_user()
@@ -2063,7 +2066,7 @@ def create_user_robot(robot_shortname):
   return resp
 
 
-@app.route('/api/organization/<orgname>/robots/<robot_shortname>',
+@api.route('/organization/<orgname>/robots/<robot_shortname>',
            methods=['PUT'])
 @api_login_required
 @org_api_call('create_user_robot')
@@ -2080,7 +2083,7 @@ def create_org_robot(orgname, robot_shortname):
   abort(403)
 
 
-@app.route('/api/user/robots/<robot_shortname>', methods=['DELETE'])
+@api.route('/user/robots/<robot_shortname>', methods=['DELETE'])
 @api_login_required
 def delete_user_robot(robot_shortname):
   parent = current_user.db_user()
@@ -2089,7 +2092,7 @@ def delete_user_robot(robot_shortname):
   return make_response('No Content', 204)
 
 
-@app.route('/api/organization/<orgname>/robots/<robot_shortname>',
+@api.route('/organization/<orgname>/robots/<robot_shortname>',
            methods=['DELETE'])
 @api_login_required
 @org_api_call('delete_user_robot')
@@ -2122,7 +2125,7 @@ def log_view(log):
 
 
 
-@app.route('/api/repository/<path:repository>/logs', methods=['GET'])
+@api.route('/repository/<path:repository>/logs', methods=['GET'])
 @api_login_required
 @parse_repository_name
 def list_repo_logs(namespace, repository):
@@ -2139,7 +2142,7 @@ def list_repo_logs(namespace, repository):
   abort(403)
 
 
-@app.route('/api/organization/<orgname>/logs', methods=['GET'])
+@api.route('/organization/<orgname>/logs', methods=['GET'])
 @api_login_required
 @org_api_call('list_user_logs')
 def list_org_logs(orgname):
@@ -2155,7 +2158,7 @@ def list_org_logs(orgname):
   abort(403)
 
 
-@app.route('/api/user/logs', methods=['GET'])
+@api.route('/user/logs', methods=['GET'])
 @api_login_required
 def list_user_logs():
   performer_name = request.args.get('performer', None)
diff --git a/endpoints/common.py b/endpoints/common.py
index d6bd0125a..2648f5e9c 100644
--- a/endpoints/common.py
+++ b/endpoints/common.py
@@ -1,5 +1,8 @@
 import logging
+import os
+import base64
 
+from flask import request, abort, session
 from flask.ext.login import login_user, UserMixin
 from flask.ext.principal import identity_changed
 
@@ -46,3 +49,22 @@ def common_login(db_user):
   else:
     logger.debug('User could not be logged in, inactive?.')
     return False
+
+
+@app.errorhandler(model.DataModelException)
+def handle_dme(ex):
+  return make_response(ex.message, 400)
+
+
+@app.errorhandler(KeyError)
+def handle_dme_key_error(ex):
+  return make_response(ex.message, 400)
+
+
+def generate_csrf_token():
+  if '_csrf_token' not in session:
+    session['_csrf_token'] = base64.b64encode(os.urandom(48))
+
+  return session['_csrf_token']
+
+app.jinja_env.globals['csrf_token'] = generate_csrf_token 
diff --git a/endpoints/index.py b/endpoints/index.py
index f07405092..b3896bf91 100644
--- a/endpoints/index.py
+++ b/endpoints/index.py
@@ -2,7 +2,7 @@ import json
 import logging
 import urlparse
 
-from flask import request, make_response, jsonify, abort, session
+from flask import request, make_response, jsonify, abort, session, Blueprint
 from functools import wraps
 
 from data import model
@@ -18,6 +18,7 @@ from auth.permissions import (ModifyRepositoryPermission, UserPermission,
 
 logger = logging.getLogger(__name__)
 
+index = Blueprint('index', __name__)
 
 def generate_headers(role='read'):
   def decorator_method(f):
@@ -51,8 +52,8 @@ def generate_headers(role='read'):
   return decorator_method
 
 
-@app.route('/v1/users', methods=['POST'])
-@app.route('/v1/users/', methods=['POST'])
+@index.route('/users', methods=['POST'])
+@index.route('/users/', methods=['POST'])
 def create_user():
   user_data = request.get_json()
   username = user_data['username']
@@ -87,8 +88,8 @@ def create_user():
     return make_response('Created', 201)
 
 
-@app.route('/v1/users', methods=['GET'])
-@app.route('/v1/users/', methods=['GET'])
+@index.route('/users', methods=['GET'])
+@index.route('/users/', methods=['GET'])
 @process_auth
 def get_user():
   if get_authenticated_user():  
@@ -99,7 +100,7 @@ def get_user():
   abort(404)
 
 
-@app.route('/v1/users/<username>/', methods=['PUT'])
+@index.route('/users/<username>/', methods=['PUT'])
 @process_auth
 def update_user(username):
   permission = UserPermission(username)
@@ -124,7 +125,7 @@ def update_user(username):
   abort(403)
 
 
-@app.route('/v1/repositories/<path:repository>', methods=['PUT'])
+@index.route('/repositories/<path:repository>', methods=['PUT'])
 @process_auth
 @parse_repository_name
 @generate_headers(role='write')
@@ -188,7 +189,7 @@ def create_repository(namespace, repository):
   return response
 
 
-@app.route('/v1/repositories/<path:repository>/images', methods=['PUT'])
+@index.route('/repositories/<path:repository>/images', methods=['PUT'])
 @process_auth
 @parse_repository_name
 @generate_headers(role='write')
@@ -234,7 +235,7 @@ def update_images(namespace, repository):
   abort(403)
 
 
-@app.route('/v1/repositories/<path:repository>/images', methods=['GET'])
+@index.route('/repositories/<path:repository>/images', methods=['GET'])
 @process_auth
 @parse_repository_name
 @generate_headers(role='read')
@@ -290,7 +291,7 @@ def get_repository_images(namespace, repository):
   abort(403)
 
 
-@app.route('/v1/repositories/<path:repository>/images', methods=['DELETE'])
+@index.route('/repositories/<path:repository>/images', methods=['DELETE'])
 @process_auth
 @parse_repository_name
 @generate_headers(role='write')
@@ -298,19 +299,19 @@ def delete_repository_images(namespace, repository):
   return make_response('Not Implemented', 501)
 
 
-@app.route('/v1/repositories/<path:repository>/auth', methods=['PUT'])
+@index.route('/repositories/<path:repository>/auth', methods=['PUT'])
 @parse_repository_name
 def put_repository_auth(namespace, repository):
   return make_response('Not Implemented', 501)
 
 
-@app.route('/v1/search', methods=['GET'])
+@index.route('/search', methods=['GET'])
 def get_search():
   return make_response('Not Implemented', 501)
 
 
-@app.route('/_ping')
-@app.route('/v1/_ping')
+@index.route('/_ping')
+@index.route('/_ping')
 def ping():
   response = make_response('true', 200)
   response.headers['X-Docker-Registry-Version'] = '0.6.0'
diff --git a/endpoints/registry.py b/endpoints/registry.py
index f1b6ae223..57895aebe 100644
--- a/endpoints/registry.py
+++ b/endpoints/registry.py
@@ -1,7 +1,8 @@
 import logging
 import json
 
-from flask import make_response, request, session, Response, abort, redirect
+from flask import (make_response, request, session, Response, abort,
+                   redirect, Blueprint)
 from functools import wraps
 from datetime import datetime
 from time import time
@@ -15,6 +16,7 @@ from auth.permissions import (ReadRepositoryPermission,
                               ModifyRepositoryPermission)
 from data import model
 
+registry = Blueprint('registry', __name__)
 
 store = app.config['STORAGE']
 logger = logging.getLogger(__name__)
@@ -73,7 +75,7 @@ def set_cache_headers(f):
   return wrapper
 
 
-@app.route('/v1/images/<image_id>/layer', methods=['GET'])
+@registry.route('/images/<image_id>/layer', methods=['GET'])
 @process_auth
 @extract_namespace_repo_from_session
 @require_completion
@@ -94,7 +96,7 @@ def get_image_layer(namespace, repository, image_id, headers):
   abort(403)
 
 
-@app.route('/v1/images/<image_id>/layer', methods=['PUT'])
+@registry.route('/images/<image_id>/layer', methods=['PUT'])
 @process_auth
 @extract_namespace_repo_from_session
 def put_image_layer(namespace, repository, image_id):
@@ -165,7 +167,7 @@ def put_image_layer(namespace, repository, image_id):
   return make_response('true', 200)
 
 
-@app.route('/v1/images/<image_id>/checksum', methods=['PUT'])
+@registry.route('/images/<image_id>/checksum', methods=['PUT'])
 @process_auth
 @extract_namespace_repo_from_session
 def put_image_checksum(namespace, repository, image_id):
@@ -208,7 +210,7 @@ def put_image_checksum(namespace, repository, image_id):
   return make_response('true', 200)
 
 
-@app.route('/v1/images/<image_id>/json', methods=['GET'])
+@registry.route('/images/<image_id>/json', methods=['GET'])
 @process_auth
 @extract_namespace_repo_from_session
 @require_completion
@@ -238,7 +240,7 @@ def get_image_json(namespace, repository, image_id, headers):
   return response
 
 
-@app.route('/v1/images/<image_id>/ancestry', methods=['GET'])
+@registry.route('/images/<image_id>/ancestry', methods=['GET'])
 @process_auth
 @extract_namespace_repo_from_session
 @require_completion
@@ -283,7 +285,7 @@ def store_checksum(namespace, repository, image_id, checksum):
   store.put_content(checksum_path, checksum)
 
 
-@app.route('/v1/images/<image_id>/json', methods=['PUT'])
+@registry.route('/images/<image_id>/json', methods=['PUT'])
 @process_auth
 @extract_namespace_repo_from_session
 def put_image_json(namespace, repository, image_id):
diff --git a/endpoints/tags.py b/endpoints/tags.py
index 7267d7b7e..f6b0e1163 100644
--- a/endpoints/tags.py
+++ b/endpoints/tags.py
@@ -2,7 +2,7 @@
 import logging
 import json
 
-from flask import abort, request, jsonify, make_response
+from flask import abort, request, jsonify, make_response, Blueprint
 
 from app import app
 from util.names import parse_repository_name
@@ -14,8 +14,10 @@ from data import model
 
 logger = logging.getLogger(__name__)
 
+tags = Blueprint('tags', __name__)
 
-@app.route('/v1/repositories/<path:repository>/tags',
+
+@tags.route('/repositories/<path:repository>/tags',
            methods=['GET'])
 @process_auth
 @parse_repository_name
@@ -30,7 +32,7 @@ def get_tags(namespace, repository):
   abort(403)
 
 
-@app.route('/v1/repositories/<path:repository>/tags/<tag>',
+@tags.route('/repositories/<path:repository>/tags/<tag>',
            methods=['GET'])
 @process_auth
 @parse_repository_name
@@ -46,7 +48,7 @@ def get_tag(namespace, repository, tag):
   abort(403)
 
 
-@app.route('/v1/repositories/<path:repository>/tags/<tag>',
+@tags.route('/repositories/<path:repository>/tags/<tag>',
            methods=['PUT'])
 @process_auth
 @parse_repository_name
@@ -62,7 +64,7 @@ def put_tag(namespace, repository, tag):
   abort(403)
 
 
-@app.route('/v1/repositories/<path:repository>/tags/<tag>',
+@tags.route('/repositories/<path:repository>/tags/<tag>',
            methods=['DELETE'])
 @process_auth
 @parse_repository_name
@@ -77,7 +79,7 @@ def delete_tag(namespace, repository, tag):
   abort(403)
 
 
-@app.route('/v1/repositories/<path:repository>/tags',
+@tags.route('/repositories/<path:repository>/tags',
            methods=['DELETE'])
 @process_auth
 @parse_repository_name
diff --git a/endpoints/web.py b/endpoints/web.py
index 4d4a7503d..57d15164a 100644
--- a/endpoints/web.py
+++ b/endpoints/web.py
@@ -3,7 +3,7 @@ import requests
 import stripe
 
 from flask import (abort, redirect, request, url_for, render_template,
-                   make_response, Response)
+                   make_response, Response, Blueprint)
 from flask.ext.login import login_required, current_user
 from urlparse import urlparse
 
@@ -19,23 +19,28 @@ from endpoints.common import common_login
 
 logger = logging.getLogger(__name__)
 
+web = Blueprint('web', __name__)
+
 
 def render_page_template(name, **kwargs):
-  return make_response(render_template(name, route_data=get_route_data(),
+
+  resp = make_response(render_template(name, route_data=get_route_data(),
                                        **kwargs))
+  resp.headers['X-FRAME-OPTIONS'] = 'DENY'
+  return resp
 
 
-@app.route('/', methods=['GET'], defaults={'path': ''})
-@app.route('/repository/<path:path>', methods=['GET'])
-@app.route('/organization/<path:path>', methods=['GET'])
+@web.route('/', methods=['GET'], defaults={'path': ''})
+@web.route('/repository/<path:path>', methods=['GET'])
+@web.route('/organization/<path:path>', methods=['GET'])
 @no_cache
 def index(path):
   return render_page_template('index.html')
 
 
-@app.route('/snapshot', methods=['GET'])
-@app.route('/snapshot/', methods=['GET'])
-@app.route('/snapshot/<path:path>', methods=['GET'])
+@web.route('/snapshot', methods=['GET'])
+@web.route('/snapshot/', methods=['GET'])
+@web.route('/snapshot/<path:path>', methods=['GET'])
 def snapshot(path = ''):
   parsed = urlparse(request.url)
   final_url = '%s://%s/%s' % (parsed.scheme, 'localhost', path)
@@ -46,92 +51,93 @@ def snapshot(path = ''):
   abort(404)
 
 
-@app.route('/plans/')
+@web.route('/plans/')
 @no_cache
 def plans():
   return index('')
 
 
-@app.route('/guide/')
+@web.route('/guide/')
 @no_cache
 def guide():
   return index('')
 
 
-@app.route('/organizations/')
-@app.route('/organizations/new/')
+@web.route('/organizations/')
+@web.route('/organizations/new/')
 @no_cache
 def organizations():
   return index('')
 
 
-@app.route('/user/')
+@web.route('/user/')
 @no_cache
 def user():
   return index('')
 
 
-@app.route('/signin/')
+@web.route('/signin/')
 @no_cache
 def signin():
   return index('')
 
 
-@app.route('/contact/')
+@web.route('/contact/')
+@no_cache
 def contact():
   return index('')
 
 
-@app.route('/new/')
+@web.route('/new/')
 @no_cache
 def new():
   return index('')
 
 
-@app.route('/repository/')
+@web.route('/repository/')
 @no_cache
 def repository():
   return index('')
 
 
-@app.route('/security/')
+@web.route('/security/')
 @no_cache
 def security():
   return index('')
 
 
-@app.route('/v1')
-@app.route('/v1/')
+@web.route('/v1')
+@web.route('/v1/')
 @no_cache
 def v1():
   return index('')
 
 
-@app.route('/status', methods=['GET'])
+@web.route('/status', methods=['GET'])
 @no_cache
 def status():
   return make_response('Healthy')
 
 
-@app.route('/tos', methods=['GET'])
+@web.route('/tos', methods=['GET'])
 @no_cache
 def tos():
   return render_page_template('tos.html')
 
 
-@app.route('/disclaimer', methods=['GET'])
+@web.route('/disclaimer', methods=['GET'])
 @no_cache
 def disclaimer():
   return render_page_template('disclaimer.html')
 
 
-@app.route('/privacy', methods=['GET'])
+@web.route('/privacy', methods=['GET'])
 @no_cache
 def privacy():
   return render_page_template('privacy.html')
 
 
-@app.route('/receipt', methods=['GET'])
+@web.route('/receipt', methods=['GET'])
 def receipt():
   if not current_user.is_authenticated():
     abort(401)
@@ -188,7 +194,7 @@ def get_github_user(token):
   return get_user.json()
 
 
-@app.route('/oauth2/github/callback', methods=['GET'])
+@web.route('/oauth2/github/callback', methods=['GET'])
 def github_oauth_callback():
   error = request.args.get('error', None)
   if error:
@@ -241,7 +247,7 @@ def github_oauth_callback():
   return render_page_template('githuberror.html')
 
 
-@app.route('/oauth2/github/callback/attach', methods=['GET'])
+@web.route('/oauth2/github/callback/attach', methods=['GET'])
 @login_required
 def github_oauth_attach():
   token = exchange_github_code_for_token(request.args.get('code'))
@@ -252,7 +258,7 @@ def github_oauth_attach():
   return redirect(url_for('user'))
 
 
-@app.route('/confirm', methods=['GET'])
+@web.route('/confirm', methods=['GET'])
 def confirm_email():
   code = request.values['code']
   user = None
@@ -268,7 +274,7 @@ def confirm_email():
   return redirect(url_for('user', tab='email') if new_email else url_for('index'))
 
 
-@app.route('/recovery', methods=['GET'])
+@web.route('/recovery', methods=['GET'])
 def confirm_recovery():
   code = request.values['code']
   user = model.validate_reset_code(code)
diff --git a/endpoints/webhooks.py b/endpoints/webhooks.py
index f93ef7a70..5a6c0ad3d 100644
--- a/endpoints/webhooks.py
+++ b/endpoints/webhooks.py
@@ -1,7 +1,7 @@
 import logging
 import stripe
 
-from flask import request, make_response
+from flask import request, make_response, Blueprint
 
 from data import model
 from app import app
@@ -11,8 +11,9 @@ from util.email import send_invoice_email
 
 logger = logging.getLogger(__name__)
 
+webhooks = Blueprint('webhooks', __name__)
 
-@app.route('/webhooks/stripe', methods=['POST'])
+@webhooks.route('/stripe', methods=['POST'])
 def stripe_webhook():
   request_data = request.get_json()
   logger.debug('Stripe webhook call: %s' % request_data)
diff --git a/static/js/app.js b/static/js/app.js
index 022b9f852..f89281beb 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -2512,6 +2512,10 @@ quayApp.directive('ngBlur', function() {
 
 quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanService', '$http', '$timeout',
     function($location, $rootScope, Restangular, UserService, PlanService, $http, $timeout) {
+
+  // Handle session security.
+  Restangular.setDefaultRequestParams({'_csrf_token': window.__token || ''});
+
   // Handle session expiration.
   Restangular.setErrorInterceptor(function(response) {
     if (response.status == 401) {
diff --git a/templates/base.html b/templates/base.html
index 6f40a4784..731e19d18 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -69,6 +69,7 @@
 
     <script type="text/javascript">
       window.__endpoints = {{ route_data|safe }}.endpoints;
+      window.__token = '{{ csrf_token() }}';
     </script>
 
     <script src="static/js/app.js"></script>