diff --git a/reviewboard/integrations/admin.py b/reviewboard/integrations/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c9a1cfae17e70852fe993e9dc584336accb1ff9
--- /dev/null
+++ b/reviewboard/integrations/admin.py
@@ -0,0 +1,13 @@
+from __future__ import unicode_literals
+
+from django.contrib import admin
+
+from reviewboard.integrations.models import ConfiguredIntegration
+
+
+class ConfiguredIntegrationAdmin(admin.ModelAdmin):
+    list_display = ('description', 'integration_id', 'is_enabled',)
+    raw_id_fields = ('local_site',)
+
+
+admin.site.register(ConfiguredIntegration, ConfiguredIntegrationAdmin)
diff --git a/reviewboard/integrations/urls.py b/reviewboard/integrations/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..93759831e74f221cb8196350cf17c1a537e14b0a
--- /dev/null
+++ b/reviewboard/integrations/urls.py
@@ -0,0 +1,15 @@
+from __future__ import unicode_literals
+
+from django.conf.urls import patterns, url
+
+import reviewboard.integrations.views as views
+
+
+urlpatterns = patterns(
+    '',
+    url(r'^new/(?P<integration_class>\w+)/', views.configure_integration,
+        name='new-integration'),
+    url(r'(?P<config_id>\d+)/', views.configure_integration,
+        name='configure-integration'),
+    url(r'^$', views.integration_list),
+)
diff --git a/reviewboard/integrations/views.py b/reviewboard/integrations/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f8c13f59ed6b7df2ad28982f5b85279d3b2feba
--- /dev/null
+++ b/reviewboard/integrations/views.py
@@ -0,0 +1,52 @@
+from __future__ import unicode_literals
+
+from django.views.decorators.csrf import csrf_protect
+from django.contrib.admin.views.decorators import staff_member_required
+from django.http import Http404, HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template.context import RequestContext
+
+from reviewboard.integrations.manager import get_integration_manager
+
+
+manager = get_integration_manager()
+
+
+@csrf_protect
+@staff_member_required
+def integration_list(request,
+                     template_name='integrations/integration_list.html'):
+        return render_to_response(template_name, RequestContext(request))
+
+
+@csrf_protect
+@staff_member_required
+def configure_integration(request, integration_class=None, config_id=None,
+                          template_name='integrations/configure_integration'
+                                        '.html'):
+    if config_id:
+        config_instance = manager.get_config_instance(int(config_id))
+        integration_class = config_instance.integration.integration_id
+
+    if integration_class:
+        integration = manager.get_integration(integration_class)
+
+    if not integration:
+        raise Http404
+    else:
+        form_class = integration.form
+
+    if request.method == 'POST':
+        form = form_class(integration, request.POST, request.FILES)
+
+        if form.is_valid():
+            form.save()
+
+            return HttpResponseRedirect(request.path + '?save=1')
+    else:
+        form = form_class(integration, config=config_instance)
+
+    return render_to_response(template_name, RequestContext(request, {
+        'integration_name': integration.name,
+        'form': form,
+        }))
diff --git a/reviewboard/static/rb/css/pages/integration.less b/reviewboard/static/rb/css/pages/integration.less
new file mode 100644
index 0000000000000000000000000000000000000000..623f3469be7b6ff04cd2f7ad5f8e07d2215b2644
--- /dev/null
+++ b/reviewboard/static/rb/css/pages/integration.less
@@ -0,0 +1,106 @@
+/****************************************************************************
+ * Integration manager
+ ****************************************************************************/
+
+.integration-header {
+  padding-top: 10px;
+  margin-bottom: 10px;
+}
+
+.integration-icon {
+  height: 48px;
+  width: 48px;
+  margin-left: 10px;
+  padding-bottom: 10px;
+  float: left;
+}
+
+.integration-content {
+  padding-left: 20px;
+  float: left;
+}
+
+.integration-list-page {
+  #content-main {
+    float: left;
+    width: 100%;
+  }
+}
+
+.integrations {
+  border: 1px #AAAAAA solid;
+  border-bottom: 0;
+  border-top: 0;
+  margin: 0;
+  padding: 0;
+
+  li {
+    list-style: none;
+    padding-bottom: 10px;
+    position: relative;
+  }
+
+  .integration {
+    border-bottom: 1px #AAAAAA solid;
+
+    h1 {
+      font-size: 120%;
+      margin: 0;
+    }
+
+    .description {
+      margin: 1em 10em 1em 0em;
+      padding: 0;
+      float: left;
+    }
+
+    .add {
+      display: inline-block;
+      position: absolute;
+      top: 24px;
+      right: 20px;
+    }
+
+    .integration-header {
+      display: inline-block;
+      width: 100%;
+      border-bottom: 1px solid #EEE;
+    }
+
+    .configured-integrations {
+      padding-left: 0px;
+
+      .configured-integration {
+        display: block;
+        width: 90%;
+        margin: 0px auto;
+        padding: 10px 0px 10px 48px;
+      }
+
+      .configured-header {
+        padding-bottom: 20px;
+      }
+
+      .description {
+        padding-left: 5px;
+        margin: 0px;
+      }
+
+      .options {
+        position: absolute;
+        padding-top: 10px;
+        top: 0px;
+        right: 20px;
+
+        .option {
+          padding-right: 10px;
+          display: inline-block;
+
+          span {
+            height: 14px;
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/reviewboard/static/rb/js/integrations/models/integrationManagerModel.js b/reviewboard/static/rb/js/integrations/models/integrationManagerModel.js
new file mode 100644
index 0000000000000000000000000000000000000000..2f668c330a64278a43f0320a7e7f2cc5163fad74
--- /dev/null
+++ b/reviewboard/static/rb/js/integrations/models/integrationManagerModel.js
@@ -0,0 +1,216 @@
+/*
+ * Represent an integration class listed in the Manage Integration list.
+ *
+ * This stores the various information about the integration.
+ */
+Integration = Backbone.Model.extend({
+	defaults: {
+		integrationID: null,
+		name: null,
+		description: null,
+		iconPath: null,
+		newLink: null
+	},
+
+	parse: function(rsp) {
+		return {
+			integrationID: rsp.integration_id,
+			name: rsp.name,
+			description: rsp.description,
+			iconPath: rsp.icon_path,
+			newLink: rsp.new_link
+		};
+	}
+});
+
+
+/*
+ * Represents a configured integration for a integration class.
+ *
+ * This stores the various information about the ConfiguredIntegration object
+ * provides action to enable or disable the instance.
+ */
+ConfiguredIntegration = Backbone.Model.extend({
+	defaults: {
+		id: null,
+		name: null,
+		integration: null,
+		integrationDescription: null,
+		integrationIcon: null,
+		enabled: null,
+		configuration: null,
+		configureLink: null
+	},
+
+	url: function() {
+		return SITE_ROOT + 'api/configured-integrations/' + this.id + '/';
+	},
+
+	/*
+	 * Enables the integration.
+	 */
+	enable: function() {
+			this.save({
+					enabled: true
+			}, {
+					wait: true,
+					error: function(model, xhr) {
+							alert(gettext('Failed to enable integration. ') +
+								  xhr.errorText + '.');
+					}
+			});
+	},
+
+
+	/*
+	 * Disables the integration.
+	 */
+	disable: function() {
+			this.save({
+					enabled: false
+			}, {
+					wait: true,
+					error: function(model, xhr) {
+							alert(gettext('Failed to disable integration. ') +
+								  xhr.errorText + '.');
+					}
+			});
+	},
+
+
+	/*
+	 * Returns a JSON payload for requests sent to the server.
+	 */
+	toJSON: function() {
+			return {
+					enabled: this.get('enabled')
+			};
+	},
+
+	/*
+	 * Performs AJAX requests against the server-side API.
+	 */
+	sync: function(method, model, options) {
+			Backbone.sync.call(this, method, model, _.defaults({
+					contentType: 'application/x-www-form-urlencoded',
+					data: model.toJSON(options),
+					processData: true,
+					error: _.bind(function(xhr) {
+							var rsp = null,
+									loadError,
+									text;
+
+							try {
+									rsp = $.parseJSON(xhr.responseText);
+									text = rsp.err.msg;
+									loadError = rsp.load_error;
+							} catch (e) {
+									text = 'HTTP ' + xhr.status + ' ' + xhr.statusText;
+							}
+
+							if (_.isFunction(options.error)) {
+									xhr.errorText = text;
+									xhr.errorRsp = rsp;
+									options.error(xhr, options);
+							}
+					}, this)
+			}, options));
+	},
+
+	parse: function(rsp) {
+		if (rsp.stat !== undefined) {
+			rsp = rsp.configured_integration;
+		}
+
+		return {
+			id: rsp.id,
+			name: rsp.name,
+			integration: rsp.integration_id,
+			integrationDescription: rsp.integration_description,
+			integrationIcon: rsp.integration_icon,
+			description: rsp.description,
+			enabled: rsp.is_enabled,
+			configuration: rsp.configuration,
+			configureLink: rsp.configure_link
+		};
+	}
+});
+
+
+/*
+ * A collection of Integration.
+ */
+IntegrationCollection = Backbone.Collection.extend({
+	model: Integration,
+
+	url: function() {
+		return SITE_ROOT + 'api/integrations/';
+	},
+
+	parse: function(rsp) {
+		return rsp.integrations;
+	}
+});
+
+
+/*
+ * A collection of ConfiguredIntegration.
+ */
+ConfiguredIntegrationCollection = Backbone.Collection.extend({
+	model: ConfiguredIntegration,
+
+	initialize: function(options) {
+		this.integrationID = options.integrationID;
+	},
+
+	url: function() {
+		return SITE_ROOT + 'api/configured-integrations/?integrationID=' +
+			this.integrationID;
+	},
+
+	parse: function(rsp) {
+		return rsp.configured_integrations;
+	}
+});
+
+
+/*
+ * Manages Integrations.
+ *
+ * This stores a collection of Integration, and provide functionality to load
+ * the list from the server.
+ */
+IntegrationManager = Backbone.Model.extend({
+	initialize: function() {
+		this.integrations = new IntegrationCollection();
+	},
+
+	load: function() {
+		this.integrations.fetch({
+			success: _.bind(function() {
+				this.trigger('loaded');
+			}, this)
+		});
+	}
+});
+
+
+/*
+ * Manages configured integrations.
+ *
+ * This stores a collection of ConfiguredIntegration, and provide functionality
+ * to load the list from the server.
+ */
+ConfiguredIntegrationManager = Backbone.Model.extend({
+	initialize: function(options) {
+		this.configuredIntegrations = new ConfiguredIntegrationCollection(options);
+	},
+
+	load: function() {
+		this.configuredIntegrations.fetch({
+			success: _.bind(function() {
+				this.trigger('loaded');
+			}, this)
+		});
+	}
+});
\ No newline at end of file
diff --git a/reviewboard/static/rb/js/integrations/views/integrationManagerView.js b/reviewboard/static/rb/js/integrations/views/integrationManagerView.js
new file mode 100644
index 0000000000000000000000000000000000000000..55c2305bddf153472f5d2124a007550a9db212e5
--- /dev/null
+++ b/reviewboard/static/rb/js/integrations/views/integrationManagerView.js
@@ -0,0 +1,227 @@
+/*
+ * Display an integration class in the Manage Integration list.
+ *
+ * This will show information about the Integration, and also
+ * ConfiguredIntegration which belongs to this integration class.
+ * It will also provide link to create new instance of this.
+ */
+IntegrationView = Backbone.View.extend({
+	className: 'integration',
+	tagName: 'li',
+
+	events: {
+		'click .add-new': '_addNewIntegration'
+	},
+
+	template: _.template([
+		'<div class="integration-header">',
+		'<img class="integration-icon" src="<%-iconPath%>">',
+		' <div class="integration-content"><h1><%- name %></h1>',
+		' <div class="description"><%- description %></div>',
+		' <ul class="add">',
+		'   <li><a href="<%- newLink %>">Add</a></li>',
+		' </ul></div>',
+		'</div>',
+		'<ul class="configured-integrations">',
+		'</ul>'
+	].join('')),
+
+	initialize: function() {
+		this._$configuredIntegrationsView = null;
+		this.toggle = false;
+	},
+
+	render: function() {
+		this.$el.html(this.template(this.model.attributes));
+
+		this._loadConfiguredIntegrations();
+	},
+
+	/*
+	 * Load all the configured integrations that belongs to this integration.
+	 *
+	 * This will load an configured integration manager view which will
+	 * display all the ConfiguredIntegration object that belongs to this
+	 * Integration class.
+	 */
+	_loadConfiguredIntegrations: function() {
+		this._$configuredIntegrationsView = new ConfiguredIntegrationManagerView({
+			el: this.$el,
+			model: new ConfiguredIntegrationManager({
+				integrationID: this.model.attributes.integrationID
+			})
+		});
+
+		this._$configuredIntegrationsView.render();
+	}
+});
+
+
+/*
+ * Displays an configured integration in Manage Integration list.
+ *
+ * This wil show all the information about the ConfiguredIntegration object,
+ * and provide enabling/disabling/deleting of instance. It will also
+ * provide links for configuring it.
+ */
+ConfiguredIntegrationView = Backbone.View.extend({
+	className: 'configured-integration',
+	tagName: 'li',
+
+	events: {
+		'click .configure-integration': '_configureIntegration',
+		'click .enable-toggle': '_toggleEnableState',
+		'click .delete-integration': '_deleteIntegration'
+	},
+
+	template: _.template([
+		'<div class="configured-header">',
+		'  <div class="description"><%- description %></div>',
+		'  <ul class="options">',
+		'    <li class="option"><a href="#" class="enable-toggle"></a></li>',
+		'    <li class="option">',
+		'      <a href="<%- configureLink %>" class="configure-integration">',
+		'        Configure',
+		'      </a>',
+		'    </li>',
+		'	 <li class="option">',
+		'      <a href="#" class="delete-integration">',
+		'	     <span class="ui-icon ui-icon-trash"></span>',
+		'      </a></li>',
+		' </ul>',
+		'</div>'
+	].join('')),
+
+	render: function() {
+		this.$el.html(this.template(this.model.attributes));
+
+		this._$enableToggle = this.$('.enable-toggle');
+		this.listenTo(this.model, 'change:enabled', this._showEnabledState);
+		this._showEnabledState();
+	},
+
+	/*
+	 * Updates the view to reflect the current enabled state.
+	 *
+	 * The Enable/Disable linke will change to reflect the state.
+	 */
+	_showEnabledState: function() {
+		var enabled = this.model.get('enabled');
+
+		this.$el
+			.removeClass(enabled ? 'disabled' : 'enabled')
+			.addClass(enabled ? 'enabled' : 'disabled');
+
+		this._$enableToggle
+			.text(enabled ? gettext('Disable') : gettext('Enable'));
+	},
+
+	/*
+	 * Toggle the enabled state of the ConfiguredIntegration.
+	 */
+	_toggleEnableState: function() {
+		if (this.model.get('enabled')) {
+			this.model.disable();
+		} else {
+			this.model.enable();
+		}
+
+		return false;
+	},
+
+	/*
+	 * Delete the ConfiguredIntegration object.
+	 *
+	 * This destory the ConfiguredIntegration by call HTTP DELETE
+	 * through the destory method. The ConfiguredIntegration will be removed
+	 * from the list if the response from the server is a success. If not,
+	 * it will alert the user of the error in deleting.
+	 */
+	_deleteIntegration: function() {
+		var response = confirm("This action will delete the configured"
+							 + " integration.");
+
+		if (response) {
+			this.model.destroy({
+				success: _.bind(function() {
+					this.remove();
+				}, this),
+				error: function(model, xhr) {
+					alert(gettext('Failed to delete integration. ') +
+						  xhr.errorText + '.');
+				}
+			});
+		}
+
+		return false;
+	}
+});
+
+
+/*
+ * Display the interface showing all Integrations.
+ *
+ * This loads the list of Integrations and display each in a list.
+ */
+IntegrationManagerView = Backbone.View.extend({
+	initialize: function() {
+		this._$integrations = null;
+	},
+
+	render: function() {
+		this._$integrations = this.$('.integrations');
+
+		this.listenTo(this.model, 'loaded', this._onLoaded);
+
+		this.model.load();
+
+		return this;
+	},
+
+	_onLoaded: function() {
+		this.model.integrations.each(function(integration) {
+			var view = new IntegrationView({
+				model: integration
+			});
+
+			this._$integrations.append(view.$el);
+			view.render();
+		}, this);
+	}
+});
+
+/*
+ * Display the interface showing all the ConfiguredIntegrations.
+ *
+ * This is used by the IntegrationView to generate all the
+ * ConfiguredIntegration that belongs to the integration class.
+ * It will render each ConfiguredIntegration in the list.
+ */
+ConfiguredIntegrationManagerView = Backbone.View.extend({
+	initialize: function(options) {
+		this._$configuredIntegrations = null;
+		this.integrationID = options.integrationID;
+	},
+
+	render: function() {
+		this._$configuredIntegrations = this.$('.configured-integrations');
+
+		this.listenTo(this.model, 'loaded', this._onLoaded);
+
+		this.model.load();
+
+		return this;
+	},
+
+	_onLoaded: function() {
+		this.model.configuredIntegrations.each(function(configuredIntegration) {
+			var view = new ConfiguredIntegrationView({
+				model: configuredIntegration,
+				integrationID: this.integrationID
+			});
+
+			this._$configuredIntegrations.prepend(view.$el);
+			view.render();
+		}, this);
+	}
+});
\ No newline at end of file
diff --git a/reviewboard/staticbundles.py b/reviewboard/staticbundles.py
index d0cbf0004ca7adf9e4ce713f446d6b6dcee550c0..fc5ed0d331e80a1144468a53128d69b1f4f94665 100644
--- a/reviewboard/staticbundles.py
+++ b/reviewboard/staticbundles.py
@@ -290,6 +290,13 @@ PIPELINE_JS = dict({
         ),
         'output_filename': 'rb/js/webhooks-form.min.js',
     },
+    'integrations': {
+        'source_filenames': (
+            'rb/js/integrations/views/integrationManagerView.js',
+            'rb/js/integrations/models/integrationManagerModel.js',
+        ),
+        'output_filename': 'rb/js/integrations.min.js',
+    },
 }, **DJBLETS_PIPELINE_JS)
 
 
@@ -348,6 +355,7 @@ PIPELINE_CSS = dict({
         'source_filenames': (
             'rb/css/pages/admin.less',
             'rb/css/pages/admin-dashboard.less',
+            'rb/css/pages/integration.less',
         ),
         'output_filename': 'rb/css/admin.min.css',
         'absolute_paths': False,
diff --git a/reviewboard/templates/admin/base_site.html b/reviewboard/templates/admin/base_site.html
index be03872ab783588f86d344ef80d804e38fe6afcd..bfc606afd7be0a0ae4975f6a510909d17d79b15e 100644
--- a/reviewboard/templates/admin/base_site.html
+++ b/reviewboard/templates/admin/base_site.html
@@ -55,6 +55,7 @@
  <li><a href="{% url 'reviewboard.admin.views.dashboard' %}" class="nav-first">{% trans "Admin Dashboard" %}</a></li>
  <li><a href="{% url 'reviewboard.admin.views.dashboard' %}db/">{% trans "Database" %}</a></li>
  <li><a href="{% url 'djblets.extensions.views.extension_list' %}">{% trans "Extensions" %}</a></li>
+  <li><a href="{% url 'reviewboard.integrations.views.integration_list' %}">{% trans "Integrations" %}</a></li>
 </ul>
 {%   block subnavbar %}{% endblock %}
 {%  endif %}
diff --git a/reviewboard/templates/integrations/configure_integration.html b/reviewboard/templates/integrations/configure_integration.html
new file mode 100644
index 0000000000000000000000000000000000000000..7564c8ba1494f83a45c53df8b796dc6a62ae95e4
--- /dev/null
+++ b/reviewboard/templates/integrations/configure_integration.html
@@ -0,0 +1,10 @@
+{% extends "siteconfig/settings.html" %}
+{% load djblets_utils i18n staticfiles %}
+
+{% block page_title %}
+{% blocktrans %}Configure {{integration_name}} {% endblocktrans %}
+{% endblock %}
+
+{% block form_title %}
+{%  blocktrans %}Configure {{integration_name}}{% endblocktrans %}
+{% endblock %}
\ No newline at end of file
diff --git a/reviewboard/templates/integrations/integration_list.html b/reviewboard/templates/integrations/integration_list.html
new file mode 100644
index 0000000000000000000000000000000000000000..8c25aaafd024433ecc7796875fa1ef9f8138d5bd
--- /dev/null
+++ b/reviewboard/templates/integrations/integration_list.html
@@ -0,0 +1,35 @@
+{% extends "admin/base_site.html" %}
+{% load compressed i18n staticfiles %}
+
+{% block title %}{% trans "Manage Integrations" %} {{block.super}}{% endblock %}
+
+{% block extrahead %}
+{% include "js/jquery.html" %}
+{% include "js/underscore.html" %}
+{% include "js/backbone.html" %}
+{% compressed_js "integrations" %}
+{{block.super}}
+{% endblock %}
+
+{% block content %}
+
+<h1 class="title">{% trans "Manage Integrations" %}</h1>
+
+<div id="content-main">
+  <form id="integration-manager" method="POST" action=".">
+    <ul class="integrations"></ul>
+  </form>
+</div>
+
+<script>
+    $(document).ready(function() {
+        var view = new IntegrationManagerView({
+            el: $('#integration-manager'),
+            model: new IntegrationManager()
+        });
+
+        view.render();
+    });
+</script>
+
+{% endblock %}
\ No newline at end of file
diff --git a/reviewboard/urls.py b/reviewboard/urls.py
index e3e159f8aa6a84fd0e6fb191a51ac5ce0154e3ce..b3d6766c33b980899ef4aeaa59d5366bd9332664 100644
--- a/reviewboard/urls.py
+++ b/reviewboard/urls.py
@@ -49,6 +49,7 @@ urlpatterns = patterns(
 
     (r'^admin/extensions/', include('djblets.extensions.urls'),
      {'extension_manager': extension_manager}),
+    (r'^admin/integrations/', include('reviewboard.integrations.urls')),
     (r'^admin/', include('reviewboard.admin.urls')),
 
     url(r'^jsi18n/', 'djblets.util.views.cached_javascript_catalog',
diff --git a/reviewboard/webapi/resources/__init__.py b/reviewboard/webapi/resources/__init__.py
index c708f6f62f31aa779753d19764da6faaa09144cd..52ad59dff74c882aa21d424b7b73bea8c2d57f1e 100644
--- a/reviewboard/webapi/resources/__init__.py
+++ b/reviewboard/webapi/resources/__init__.py
@@ -17,6 +17,8 @@ from reviewboard.reviews.models import (Comment, DefaultReviewer,
                                         ReviewRequestDraft, Review,
                                         ScreenshotComment, Screenshot,
                                         FileAttachmentComment)
+from reviewboard.integrations.models import ConfiguredIntegration
+from reviewboard.integrations.integration import Integration
 from reviewboard.scmtools.models import Repository
 from reviewboard.webapi.base import WebAPIResource
 from reviewboard.webapi.models import WebAPIToken
@@ -34,7 +36,6 @@ class Resources(object):
     """
     def __init__(self):
         self.extension = ExtensionResource(get_extension_manager())
-
         self._loaded = False
 
     def __getattr__(self, name):
@@ -109,7 +110,9 @@ class Resources(object):
                          self.review_file_attachment_comment))
         register_resource_for_model(User, self.user)
         register_resource_for_model(WebAPIToken, self.api_token)
-
+        register_resource_for_model(ConfiguredIntegration,
+                                    self.configured_integration),
+        register_resource_for_model(Integration, self.integration)
 
 resources = Resources()
 
diff --git a/reviewboard/webapi/resources/configured_integration.py b/reviewboard/webapi/resources/configured_integration.py
new file mode 100644
index 0000000000000000000000000000000000000000..d4879c140d67d065c0fe28602f0f86a3a3e94da7
--- /dev/null
+++ b/reviewboard/webapi/resources/configured_integration.py
@@ -0,0 +1,184 @@
+from __future__ import unicode_literals
+
+from django.contrib.staticfiles.templatetags.staticfiles import static
+from django.core.urlresolvers import reverse
+
+from djblets.util.decorators import augment_method_from
+from djblets.webapi.decorators import (webapi_login_required,
+                                       webapi_request_fields,
+                                       webapi_response_errors)
+from djblets.webapi.errors import (DOES_NOT_EXIST,
+                                   NOT_LOGGED_IN,
+                                   PERMISSION_DENIED)
+
+from reviewboard.integrations.models import ConfiguredIntegration
+from reviewboard.integrations.manager import get_integration_manager
+from reviewboard.webapi.base import WebAPIResource
+
+
+class ConfiguredIntegrationResource(WebAPIResource):
+    """Provides information on configuredIntegration resources."""
+
+    model = ConfiguredIntegration
+    name = 'configured_integration'
+
+    fields = {
+        'id': {
+            'type': int,
+            'description': 'The numeric ID of the integration.'
+        },
+        'name': {
+            'type': str,
+            'description': 'The name of the integration this resource belongs.'
+        },
+        'integration_description': {
+            'type': str,
+            'description': 'The description of the integration class.'
+        },
+        'integration_id': {
+            'type': str,
+            'description': 'The integration type that the resource belongs to.'
+        },
+        'integration_icon': {
+            'type': str,
+            'description': 'The icon path for the integration.'
+        },
+        'description': {
+            'type': str,
+            'description': 'Optional description for this resource.'
+        },
+        'is_enabled': {
+            'type': bool,
+            'description': 'Whether or not the integration is enabled.'
+        },
+        'configuration': {
+            'type': str,
+            'description': 'The configuration of the integration.'
+        },
+        'configure_link': {
+            'type': str,
+            'description': 'The URL for configurating this resource.'
+        }
+    }
+
+    uri_object_key = 'integration_id'
+    allowed_methods = ('GET', 'PUT', 'DELETE')
+
+    def __init__(self, integration_manager):
+        super(ConfiguredIntegrationResource, self).__init__()
+        self._integration_manager = integration_manager
+
+    def serialize_name_field(self, config, *args, **kwargs):
+        if not config or not config.integration:
+            return None
+        else:
+            return config.integration.name
+
+    def serialize_integration_description_field(self, config, *args, **kwargs):
+        if not config or not config.integration:
+            return None
+        else:
+            return config.integration.description
+
+    def serialize_integration_icon_field(self, config, *args, **kwargs):
+        if not config or not config.integration:
+            return None
+        else:
+            return static('ext/%s/%s' % (config.integration.extension.id,
+                                         config.integration.icon_path))
+
+    def serialize_configure_link_field(self, config, *args, **kwargs):
+        if not config:
+            return None
+        else:
+            return reverse('configure-integration', args=(config.pk,))
+
+    def has_access_permissions(self, request, config, *args, **kwargs):
+        return config.is_accessible_by(request.user)
+
+    def get_queryset(self, request, is_list=False, *args, **kwargs):
+        """Returns a queryset for the configuredIntegration models.
+
+        This will returns all the model object by default. However, it accepts
+        additional query paramater "integrationID" to filter down the list
+        for each integration class.
+        """
+        queryset = self.model.objects.all()
+
+        if is_list:
+            if 'integrationID' in request.GET:
+                queryset = queryset.filter(
+                    integration_id=request.GET.get('integrationID'))
+
+        return queryset
+
+    @webapi_login_required
+    @augment_method_from(WebAPIResource)
+    def get(self, request, *args, **kwargs):
+        pass
+
+    @webapi_login_required
+    @augment_method_from(WebAPIResource)
+    def get_list(self, request, *args, **kwargs):
+        pass
+
+    @webapi_login_required
+    @webapi_response_errors(DOES_NOT_EXIST, NOT_LOGGED_IN, PERMISSION_DENIED)
+    @webapi_request_fields(
+        required={
+            'enabled': {
+                'type': bool,
+                'description': 'Whether or not to make the extension active.'
+            },
+        },
+    )
+    def update(self, request, *args, **kwargs):
+        """Update the state of the ConfiguredIntegration.
+
+        This will enable or disable the ConfiguredIntegration instance
+        with the initilize or shutdown method from the Integration class.
+        """
+        try:
+            config = self.get_object(request, *args, **kwargs)
+        except:
+            return DOES_NOT_EXIST
+
+        if not self.has_access_permissions(request, config, *args, **kwargs):
+            return self.get_no_access_error(request, obj=config, *args,
+                                            **kwargs)
+
+        if kwargs.get('enabled'):
+            self._integration_manager.enable_config(config.pk)
+        else:
+            self._integration_manager.disable_config(config.pk)
+
+        config = ConfiguredIntegration.objects.get(pk=config.pk)
+
+        return 200, {
+            self.item_result_key: config
+        }
+
+    @webapi_login_required
+    @webapi_response_errors(DOES_NOT_EXIST, NOT_LOGGED_IN, PERMISSION_DENIED)
+    def delete(self, request, *args, **kwargs):
+        """Handle DELETE of configured integration with integration manager.
+
+        This is used to delete a configured integration object if the user
+        has permissions to do so.
+        """
+        try:
+            config = self.get_object(request, *args, **kwargs)
+        except:
+            return DOES_NOT_EXIST
+
+        if not self.has_access_permissions(request, config, *args, **kwargs):
+            return self.get_no_access_error(request, obj=config, *args,
+                                            **kwargs)
+
+        self._integration_manager.delete_config(config.pk)
+
+        return 204, {}
+
+
+configured_integration_resource = ConfiguredIntegrationResource(
+    get_integration_manager())
diff --git a/reviewboard/webapi/resources/integration.py b/reviewboard/webapi/resources/integration.py
new file mode 100644
index 0000000000000000000000000000000000000000..08012f8a67983eaf1e99258f5d8fa98fd7cc96f1
--- /dev/null
+++ b/reviewboard/webapi/resources/integration.py
@@ -0,0 +1,82 @@
+from __future__ import unicode_literals
+
+from djblets.webapi.core import WebAPIResponse
+from djblets.webapi.decorators import (webapi_login_required,
+                                       webapi_response_errors)
+from djblets.webapi.errors import NOT_LOGGED_IN, PERMISSION_DENIED
+
+from django.core.urlresolvers import reverse
+from django.contrib.staticfiles.templatetags.staticfiles import static
+
+from reviewboard.integrations.manager import get_integration_manager
+from reviewboard.webapi.base import WebAPIResource
+
+
+class IntegrationResource(WebAPIResource):
+    """Provides information on Integration resources."""
+
+    name = 'integration'
+
+    fields = {
+        'integration_id': {
+            'type': str,
+            'description': 'The unique id of the integration.'
+        },
+        'name': {
+            'type': str,
+            'description': 'The name of the integration.'
+        },
+        'description': {
+            'type': str,
+            'description': 'The description of the integration.'
+        },
+        'allows_localsites': {
+            'type': bool,
+            'description': 'Whether or not the integration can be configure\
+                            for a local site individually'
+        },
+        'needs_authentication': {
+            'type': bool,
+            'description': 'If the integration need to be authenticate.'
+        },
+        'icon_path': {
+            'type': str,
+            'description': 'The url for the icon.'
+        },
+        'new_link': {
+            'type': str,
+            'description': "The url for adding new integration."
+        }
+    }
+
+    allowed_methods = ('GET')
+
+    def __init__(self, integration_manager):
+        super(IntegrationResource, self).__init__()
+        self._integration_manager = integration_manager
+
+    def serialize_icon_path_field(self, integration, *args, **kwargs):
+        if not integration.extension or not integration.icon_path:
+            return None
+        else:
+            return static('ext/%s/%s' % (integration.extension.id,
+                                         integration.icon_path))
+
+    def serialize_new_link_field(self, integration, *args, **kwargs):
+        if not integration:
+            return None
+        else:
+            return reverse('new-integration',
+                           args=(integration.integration_id,))
+
+    @webapi_login_required
+    @webapi_response_errors(NOT_LOGGED_IN, PERMISSION_DENIED)
+    def get_list(self, request, *args, **kwargs):
+        data = list(map(lambda obj:
+                        self.serialize_object(obj, request=request),
+                        self._integration_manager.get_integrations()))
+
+        return WebAPIResponse(request, {'integrations': data})
+
+
+integration_resource = IntegrationResource(get_integration_manager())
diff --git a/reviewboard/webapi/resources/root.py b/reviewboard/webapi/resources/root.py
index d19cb1dd0ab520e7f8d6b0f6d19a21bc0951a5be..82570183b19b464f13fdba842661314f3d2bb976 100644
--- a/reviewboard/webapi/resources/root.py
+++ b/reviewboard/webapi/resources/root.py
@@ -26,10 +26,12 @@ class RootResource(WebAPIResource, DjbletsRootResource):
 
     def __init__(self, *args, **kwargs):
         super(RootResource, self).__init__([
+            resources.configured_integration,
             resources.default_reviewer,
             resources.extension,
             resources.hosting_service,
             resources.hosting_service_account,
+            resources.integration,
             resources.repository,
             resources.review_group,
             resources.review_request,
