diff --git a/endpoints/api/__init__.py b/endpoints/api/__init__.py
index c411cf091..e38a6b47e 100644
--- a/endpoints/api/__init__.py
+++ b/endpoints/api/__init__.py
@@ -175,4 +175,7 @@ import endpoints.api.legacy
 
 import endpoints.api.repository
 import endpoints.api.discovery
-import endpoints.api.user
\ No newline at end of file
+import endpoints.api.user
+import endpoints.api.search
+import endpoints.api.build
+import endpoints.api.webhook
diff --git a/endpoints/api/build.py b/endpoints/api/build.py
new file mode 100644
index 000000000..ace627433
--- /dev/null
+++ b/endpoints/api/build.py
@@ -0,0 +1,150 @@
+import logging
+import json
+
+from flask import request, url_for
+from flask.ext.restful import abort
+
+from app import app
+from endpoints.api import (RepositoryParamResource, parse_args, query_param, nickname, resource,
+                           require_repo_read, require_repo_write, validate_json_request)
+from endpoints.common import start_build
+from data import model
+from auth.permissions import ModifyRepositoryPermission
+
+
+logger = logging.getLogger(__name__)
+user_files = app.config['USERFILES']
+build_logs = app.config['BUILDLOGS']
+
+
+def build_status_view(build_obj, can_write=False):
+  status = build_logs.get_status(build_obj.uuid)
+  logger.debug('Can write: %s job_config: %s', can_write, build_obj.job_config)
+  build_obj.job_config = None
+  resp = {
+    'id': build_obj.uuid,
+    'phase': build_obj.phase,
+    'started': build_obj.started,
+    'display_name': build_obj.display_name,
+    'status': status,
+    'job_config': json.loads(build_obj.job_config) if can_write else None,
+    'is_writer': can_write,
+    'trigger': trigger_view(build_obj.trigger),
+    'resource_key': build_obj.resource_key,
+  }
+  if can_write:
+    resp['archive_url'] = user_files.get_file_url(build.resource_key)
+
+  return resp
+
+
+@resource('/v1/repository/<path:repository>/build/')
+class RepositoryBuildList(RepositoryParamResource):
+  """ Resource related to creating and listing repository builds. """
+  schemas = {
+    'RepositoryBuildRequest': {
+      'id': 'RepositoryBuildRequest',
+      'type': 'object',
+      'description': 'Description of a new repository build.',
+      'required': True,
+      'properties': {
+        'file_id': {
+          'type': 'string',
+          'description': 'The file id that was generated when the build spec was uploaded',
+          'required': True,
+        },
+        'subdirectory': {
+          'type': 'string',
+          'description': 'Subdirectory in which the Dockerfile can be found',
+        },
+      },
+    },
+  }
+
+  @parse_args
+  @query_param('limit', 'The maximum number of builds to return', type=int, default=5)
+  @require_repo_read
+  @nickname('getRepoBuilds')
+  def get(self, args, namespace, repository):
+    """ Get the list of repository builds. """
+    limit = args['limit']
+    builds = list(model.list_repository_builds(namespace, repository, limit))
+
+    can_write = ModifyRepositoryPermission(namespace, repository).can()
+    return {
+      'builds': [build_status_view(build, can_write) for build in builds]
+    }
+
+  @require_repo_write
+  @nickname('requestRepoBuild')
+  @validate_json_request('RepositoryBuildRequest')
+  def post(self, namespace, repository):
+    """ Request that a repository be built and pushed from the specified input. """
+    logger.debug('User requested repository initialization.')
+    request_json = request.get_json()
+
+    dockerfile_id = request_json['file_id']
+    subdir = request_json['subdirectory'] if 'subdirectory' in request_json else ''
+
+    # Check if the dockerfile resource has already been used. If so, then it
+    # can only be reused if the user has access to the repository for which it
+    # was used.
+    associated_repository = model.get_repository_for_resource(dockerfile_id)
+    if associated_repository:
+      if not ModifyRepositoryPermission(associated_repository.namespace,
+                                        associated_repository.name):
+        abort(403)
+
+    # Start the build.
+    repo = model.get_repository(namespace, repository)
+    display_name = user_files.get_file_checksum(dockerfile_id)
+
+    build_request = start_build(repo, dockerfile_id, ['latest'], display_name,
+                                subdir, True)
+
+    resp = build_status_view(build_request, True)
+    repo_string = '%s/%s' % (namespace, repository)
+    headers = {
+      'Location': url_for('api_bp.get_repo_build_status', repository=repo_string,
+                          build_uuid=build_request.uuid),
+    }
+    return resp, 201, headers
+
+
+@resource('/v1/repository/<path:repository>/build/<build_uuid>/status')
+class RepositoryBuildStatus(RepositoryParamResource):
+  """ Resource for dealing with repository build status. """
+  @require_repo_read
+  @nickname('getRepoBuildStatus')
+  def get(self, namespace, repository, build_uuid):
+    """ Return the status for the builds specified by the build uuids. """
+    build = model.get_repository_build(namespace, repository, build_uuid)
+    if not build:
+      abort(404)
+      
+    can_write = ModifyRepositoryPermission(namespace, repository).can()
+    return build_status_view(build, can_write)
+
+
+@resource('/repository/<path:repository>/build/<build_uuid>/logs')
+class RepositoryBuildLogs(RepositoryParamResource):
+  """ Resource for loading repository build logs. """
+  @require_repo_write
+  @nickname('getRepoBuildLogs')
+  def get(self, namespace, repository, build_uuid):
+    """ Return the build logs for the build specified by the build uuid. """
+    response_obj = {}
+
+    build = model.get_repository_build(namespace, repository, build_uuid)
+
+    start = int(request.args.get('start', 0))
+
+    count, logs = build_logs.get_log_entries(build.uuid, start)
+
+    response_obj.update({
+      'start': start,
+      'total': count,
+      'logs': [log for log in logs],
+    })
+
+    return response_obj
diff --git a/endpoints/api/legacy.py b/endpoints/api/legacy.py
index 60917ebea..c2c75b7f1 100644
--- a/endpoints/api/legacy.py
+++ b/endpoints/api/legacy.py
@@ -339,6 +339,7 @@ def request_recovery_email():
   return make_response('Created', 201)
 
 
+# Ported
 @api_bp.route('/entities/<prefix>', methods=['GET'])
 @api_login_required
 def get_matching_entities(prefix):
@@ -919,6 +920,7 @@ def create_repo():
   abort(403)
 
 
+# Ported
 @api_bp.route('/find/repository', methods=['GET'])
 def find_repos():
   prefix = request.args.get('query', '')
@@ -1158,6 +1160,7 @@ def build_status_view(build_obj, can_write=False):
   }
 
 
+# Ported
 @api_bp.route('/repository/<path:repository>/build/', methods=['GET'])
 @parse_repository_name
 def get_repo_builds(namespace, repository):
@@ -1175,6 +1178,7 @@ def get_repo_builds(namespace, repository):
   abort(403)  # Permission denied
 
 
+# Ported
 @api_bp.route('/repository/<path:repository>/build/<build_uuid>/status',
            methods=['GET'])
 @parse_repository_name
@@ -1192,6 +1196,7 @@ def get_repo_build_status(namespace, repository, build_uuid):
   abort(403)  # Permission denied
 
 
+# Ported and merged with status
 @api_bp.route('/repository/<path:repository>/build/<build_uuid>/archiveurl',
            methods=['GET'])
 @parse_repository_name
@@ -1210,6 +1215,7 @@ def get_repo_build_archive_url(namespace, repository, build_uuid):
   abort(403)  # Permission denied
 
 
+# Ported
 @api_bp.route('/repository/<path:repository>/build/<build_uuid>/logs',
            methods=['GET'])
 @parse_repository_name
@@ -1235,6 +1241,7 @@ def get_repo_build_logs(namespace, repository, build_uuid):
   abort(403)  # Permission denied
 
 
+# Ported
 @api_bp.route('/repository/<path:repository>/build/', methods=['POST'])
 @api_login_required
 @parse_repository_name
@@ -1281,6 +1288,7 @@ def webhook_view(webhook):
   }
 
 
+# Ported
 @api_bp.route('/repository/<path:repository>/webhook/', methods=['POST'])
 @api_login_required
 @parse_repository_name
@@ -1302,6 +1310,7 @@ def create_webhook(namespace, repository):
   abort(403)  # Permissions denied
 
 
+# Ported
 @api_bp.route('/repository/<path:repository>/webhook/<public_id>',
            methods=['GET'])
 @api_login_required
@@ -1319,6 +1328,7 @@ def get_webhook(namespace, repository, public_id):
   abort(403)  # Permission denied
 
 
+# Ported
 @api_bp.route('/repository/<path:repository>/webhook/', methods=['GET'])
 @api_login_required
 @parse_repository_name
@@ -1333,6 +1343,7 @@ def list_webhooks(namespace, repository):
   abort(403)  # Permission denied
 
 
+# Ported
 @api_bp.route('/repository/<path:repository>/webhook/<public_id>',
            methods=['DELETE'])
 @api_login_required
diff --git a/endpoints/api/repository.py b/endpoints/api/repository.py
index 7710c8c0e..cf49ff71e 100644
--- a/endpoints/api/repository.py
+++ b/endpoints/api/repository.py
@@ -53,8 +53,8 @@ class RepositoryList(ApiResource):
           'type': 'string',
           'description': 'Markdown encoded description for the repository',
         },
-      }
-    }
+      },
+    },
   }
 
   @require_scope(scopes.CREATE_REPO)
diff --git a/endpoints/api/search.py b/endpoints/api/search.py
new file mode 100644
index 000000000..d409d39ff
--- /dev/null
+++ b/endpoints/api/search.py
@@ -0,0 +1,105 @@
+from endpoints.api import ApiResource, parse_args, query_param, truthy_bool, nickname, resource
+from data import model
+from auth.permissions import OrganizationMemberPermission, ViewTeamPermission
+from auth.auth_context import get_authenticated_user
+
+
+@resource('/v1/entities/<prefix>')
+class EntitySearch(ApiResource):
+  """ Resource for searching entities. """
+  @parse_args
+  @query_param('namespace', 'Namespace to use when querying for org entities.', type=str,
+               default='')
+  @query_param('includeTeams', 'Whether to include team names.', type=truthy_bool, default=False)
+  @nickname('getMatchingEntities')
+  def get(self, args, prefix):
+    """ Get a list of entities that match the specified prefix. """
+    teams = []
+
+    namespace_name = args['namespace']
+    robot_namespace = None
+    organization = None
+
+    try:
+      organization = model.get_organization(namespace_name)
+
+      # namespace name was an org
+      permission = OrganizationMemberPermission(namespace_name)
+      if permission.can():
+        robot_namespace = namespace_name
+
+        if args['includeTeams']:
+          teams = model.get_matching_teams(prefix, organization)  
+
+    except model.InvalidOrganizationException:
+      # namespace name was a user
+      if get_authenticated_user().username == namespace_name:
+        robot_namespace = namespace_name
+
+    users = model.get_matching_users(prefix, robot_namespace, organization)
+
+    def entity_team_view(team):
+      result = {
+        'name': team.name,
+        'kind': 'team',
+        'is_org_member': True
+      }
+      return result
+
+    def user_view(user):
+      user_json = {
+        'name': user.username,
+        'kind': 'user',
+        'is_robot': user.is_robot,
+      }
+
+      if organization is not None:
+        user_json['is_org_member'] = user.is_robot or user.is_org_member
+
+      return user_json
+
+    team_data = [entity_team_view(team) for team in teams]
+    user_data = [user_view(user) for user in users]
+
+    return {
+      'results': team_data + user_data
+    }
+
+
+  def team_view(orgname, team):
+    view_permission = ViewTeamPermission(orgname, team.name)
+    role = model.get_team_org_role(team).name
+    return {
+      'id': team.id,
+      'name': team.name,
+      'description': team.description,
+      'can_view': view_permission.can(),
+      'role': role
+    }
+
+
+@resource('/v1/find/repository')
+class FindRepositories(ApiResource):
+  """ Resource for finding repositories. """
+  @parse_args
+  @query_param('query', 'The prefix to use when querying for repositories.', type=str, default='')
+  @nickname('findRepos')
+  def get(self, args):
+    """ Get a list of repositories that match the specified prefix query. """
+    prefix = args['query']
+
+    def repo_view(repo):
+      return {
+        'namespace': repo.namespace,
+        'name': repo.name,
+        'description': repo.description
+      }
+
+    username = None
+    if get_authenticated_user() is not None:
+      username = get_authenticated_user().username
+
+    matching = model.get_matching_repositories(prefix, username)
+    return {
+      'repositories': [repo_view(repo) for repo in matching]
+    }
\ No newline at end of file
diff --git a/endpoints/api/user.py b/endpoints/api/user.py
index 1c0bc6c7e..abbf62dca 100644
--- a/endpoints/api/user.py
+++ b/endpoints/api/user.py
@@ -156,6 +156,7 @@ class User(ApiResource):
   @nickname('createNewUser')
   @validate_json_request('NewUser')
   def post(self):
+    """ Create a new user. """
     user_data = request.get_json()
 
     existing_user = model.get_user(user_data['username'])
diff --git a/endpoints/api/webhook.py b/endpoints/api/webhook.py
new file mode 100644
index 000000000..b59919b2e
--- /dev/null
+++ b/endpoints/api/webhook.py
@@ -0,0 +1,69 @@
+from flask import request, url_for
+from flask.ext.restful import abort
+
+from endpoints.api import (RepositoryParamResource, nickname, resource, require_repo_admin,
+                           log_action)
+from data import model
+
+
+def webhook_view(webhook):
+  return {
+    'public_id': webhook.public_id,
+    'parameters': json.loads(webhook.parameters),
+  }
+
+
+@resource('/v1/repository/<path:repository>/webhook/')
+class WebhookList(RepositoryParamResource):
+  """ Resource for dealing with listing and creating webhooks. """
+
+  @require_repo_admin
+  @nickname('createWebhook')
+  def post(self, namespace, repository):
+    """ Create a new webhook for the specified repository. """
+    repo = model.get_repository(namespace, repository)
+    webhook = model.create_webhook(repo, request.get_json())
+    resp = webhook_view(webhook)
+    repo_string = '%s/%s' % (namespace, repository)
+    headers = {
+      'Location': url_for('api_bp.get_webhook', repository=repo_string,
+                          public_id=webhook.public_id),
+    }
+    log_action('add_repo_webhook', namespace, 
+               {'repo': repository, 'webhook_id': webhook.public_id},
+               repo=repo)
+    return resp, 201, headers
+
+  @require_repo_admin
+  @nickname('listWebhooks')
+  def get(self, namespace, repository):
+    """ List the webhooks for the specified repository. """
+    webhooks = model.list_webhooks(namespace, repository)
+    return {
+      'webhooks': [webhook_view(webhook) for webhook in webhooks]
+    }
+
+
+@resource('/v1/repository/<path:repository>/webhook/<public_id>')
+class Webhook(RepositoryParamResource):
+  """ Resource for dealing with specific webhooks. """
+  @require_repo_admin
+  @nickname('getWebhook')
+  def get(self, namespace, repository, public_id):
+    """ Get information for the specified webhook. """
+    try:
+      webhook = model.get_webhook(namespace, repository, public_id)
+    except model.InvalidWebhookException:
+      abort(404)
+
+    return webhook_view(webhook)
+
+  @require_repo_admin
+  @nickname('deleteWebhook')
+  def delete(self, namespace, repository, public_id):
+    """ Delete the specified webhook. """
+    model.delete_webhook(namespace, repository, public_id)
+    log_action('delete_repo_webhook', namespace, 
+               {'repo': repository, 'webhook_id': public_id},
+               repo=model.get_repository(namespace, repository))
+    return make_response('No Content', 204)