diff --git a/docs/manual/fixtures/initial_data.json b/docs/manual/fixtures/initial_data.json
index 96acaa5ba5dcaa994bfc3769c0d4f85cfd27ff55..318c99899556292a78505953e44a2c0c96e99068 100644
--- a/docs/manual/fixtures/initial_data.json
+++ b/docs/manual/fixtures/initial_data.json
@@ -2582,7 +2582,7 @@
     "fields": {
         "username": "testuser", 
         "local_site": null, 
-        "service_name": "", 
+        "service_name": "github", 
         "visible": true, 
         "hosting_url": null, 
         "data": "{}"
diff --git a/docs/manual/webapi/2.0/resources/index.rst b/docs/manual/webapi/2.0/resources/index.rst
index 632f5de2f999de8b0fa5682344f3b5cce1de4563..eca1c69e577402f2a490e1193c5729d9b8c400f9 100644
--- a/docs/manual/webapi/2.0/resources/index.rst
+++ b/docs/manual/webapi/2.0/resources/index.rst
@@ -57,6 +57,8 @@ Hosting Services
    hosting-service
    hosting-service-account-list
    hosting-service-account
+   remote-repository-list
+   remote-repository
 
 
 Repositories
diff --git a/docs/manual/webapi/2.0/resources/remote-repository-list.rst b/docs/manual/webapi/2.0/resources/remote-repository-list.rst
new file mode 100644
index 0000000000000000000000000000000000000000..4bcf5c1c561321c1295e933847d41ae00b408de3
--- /dev/null
+++ b/docs/manual/webapi/2.0/resources/remote-repository-list.rst
@@ -0,0 +1,6 @@
+.. versionadded:: 2.1
+.. webapi-resource::
+   :classname: reviewboard.webapi.resources.remote_repository.RemoteRepositoryResource
+   :is-list:
+   :hide-examples:
+   :hide-links:
diff --git a/docs/manual/webapi/2.0/resources/remote-repository.rst b/docs/manual/webapi/2.0/resources/remote-repository.rst
new file mode 100644
index 0000000000000000000000000000000000000000..56ffbbfe43633960362b0bff5646eed7dbab8f2f
--- /dev/null
+++ b/docs/manual/webapi/2.0/resources/remote-repository.rst
@@ -0,0 +1,5 @@
+.. versionadded:: 2.1
+.. webapi-resource::
+   :classname: reviewboard.webapi.resources.remote_repository.RemoteRepositoryResource
+   :hide-examples:
+   :hide-links:
diff --git a/reviewboard/testing/testcase.py b/reviewboard/testing/testcase.py
index 600f82e73805f2e649a219e49182363c782ec0eb..7686253846042841e32fd84e1c7ac0466a00e8a8 100644
--- a/reviewboard/testing/testcase.py
+++ b/reviewboard/testing/testcase.py
@@ -67,6 +67,13 @@ class TestCase(DjbletsTestCase):
 
         return doc
 
+    def get_local_site_or_none(self, name):
+        """Returns a LocalSite matching the name, if provided, or None."""
+        if name:
+            return LocalSite.objects.get(name=name)
+        else:
+            return None
+
     def create_diff_file_attachment(self, filediff, from_modified=True,
                                     review_request=None,
                                     orig_filename='filename.png',
diff --git a/reviewboard/webapi/resources/hosting_service_account.py b/reviewboard/webapi/resources/hosting_service_account.py
index 9221d6e336b4240570db7042efbdee5956ce074f..b74cf33969180f79e9e59f3e45c9fb23c23ffd54 100644
--- a/reviewboard/webapi/resources/hosting_service_account.py
+++ b/reviewboard/webapi/resources/hosting_service_account.py
@@ -20,6 +20,7 @@ from reviewboard.webapi.errors import (BAD_HOST_KEY,
                                        SERVER_CONFIG_ERROR,
                                        UNVERIFIED_HOST_CERT,
                                        UNVERIFIED_HOST_KEY)
+from reviewboard.webapi.resources import resources
 
 
 class HostingServiceAccountResource(WebAPIResource):
@@ -47,7 +48,11 @@ class HostingServiceAccountResource(WebAPIResource):
     uri_object_key = 'account_id'
     autogenerate_etags = True
 
-    allowed_methods = ('GET', 'POST',)
+    allowed_methods = ('GET', 'POST')
+
+    item_child_resources = [
+        resources.remote_repository,
+    ]
 
     @webapi_check_login_required
     def get_queryset(self, request, local_site_name=None, is_list=False,
diff --git a/reviewboard/webapi/resources/remote_repository.py b/reviewboard/webapi/resources/remote_repository.py
new file mode 100644
index 0000000000000000000000000000000000000000..f78ce938bfef5eed8bafd4b99cdb4fec83642034
--- /dev/null
+++ b/reviewboard/webapi/resources/remote_repository.py
@@ -0,0 +1,217 @@
+from __future__ import unicode_literals
+
+from django.utils import six
+from djblets.db.query import LocalDataQuerySet
+from djblets.util.decorators import augment_method_from
+from djblets.webapi.core import WebAPIResponsePaginated
+from djblets.webapi.decorators import webapi_request_fields
+
+from reviewboard.hostingsvcs.repository import RemoteRepository
+from reviewboard.webapi.base import WebAPIResource
+from reviewboard.webapi.resources import resources
+
+
+class RemoteRepositoryResponsePaginated(WebAPIResponsePaginated):
+    """Provides paginated reponses for lists of RemoteRepository objects.
+
+    This is a specialization of WebAPIResponsePaginated designed to
+    return lsits of RemoteRepository objects and to handle pagination in
+    a way that's compatible with the pagination models of HostingService.
+    """
+    def __init__(self, request, queryset, *args, **kwargs):
+        self.paginator = queryset[0]
+
+        super(RemoteRepositoryResponsePaginated, self).__init__(
+            request, queryset=None, *args, **kwargs)
+
+    def has_prev(self):
+        return self.paginator.has_prev
+
+    def has_next(self):
+        return self.paginator.has_next
+
+    def get_prev_index(self):
+        return max(self.start - 1, 0)
+
+    def get_next_index(self):
+        return self.start + 1
+
+    def get_results(self):
+        return self.paginator.page_data
+
+    def get_total_results(self):
+        return None
+
+    def build_pagination_url(self, full_path, start, max_results,
+                             query_parameters):
+        return '%s?start=%s%s' % (full_path, start, query_parameters)
+
+
+class RemoteRepositoryResource(WebAPIResource):
+    """Returns information on remote repositories on a hosting service.
+
+    This can be used to look up the information needed to connect to a
+    remote repository or to add a repository to Review Board. Only remote
+    repositories that are accessible to the linked hosting service account
+    (i.e., that of the parent resource) will be provided by this resource.
+    """
+    name = 'remote_repository'
+    name_plural = 'remote_repositories'
+
+    model = RemoteRepository
+    model_object_key = 'id'
+    model_parent_key = 'hosting_service_account'
+    uri_object_key = 'repository_id'
+    uri_object_key_regex = r'[A-Za-z0-9_./-]+'
+
+    paginated_cls = RemoteRepositoryResponsePaginated
+
+    fields = {
+        'id': {
+            'type': six.text_type,
+            'description': 'The unique ID for this repository on the '
+                           'hosting service.',
+        },
+        'name': {
+            'type': six.text_type,
+            'description': 'The name of the repository.',
+        },
+        'owner': {
+            'type': six.text_type,
+            'description': 'The owner of the repository, which may be a user '
+                           'account or an organization, depending on the '
+                           'service.',
+        },
+        'scm_type': {
+            'type': six.text_type,
+            'description': 'The type of repository, mapping to registered '
+                           'SCMTools on Review Board.',
+        },
+        'path': {
+            'type': six.text_type,
+            'description': 'The repository path as recommended by the hosting '
+                           'service.',
+        },
+        'mirror_path': {
+            'type': six.text_type,
+            'description': 'A secondary path that can be used to reach the '
+                           'repository.',
+        },
+    }
+    uri_object_key = 'repository_id'
+    autogenerate_etags = True
+
+    allowed_methods = ('GET',)
+
+    def has_list_access_permissions(self, request, *args, **kwargs):
+        account = resources.hosting_service_account.get_object(
+            request, *args, **kwargs)
+
+        # Only allow administrators or those with the ability to modify the
+        # account to see what repositories are listed.
+        return account.is_mutable_by(request.user)
+
+    def has_access_permissions(self, request, remote_repository,
+                               *args, **kwargs):
+        # Only allow administrators or those with the ability to modify the
+        # account to see what repositories are listed.
+        return remote_repository.hosting_service_account.is_mutable_by(
+            request.user)
+
+    def get_queryset(self, request, start=None, is_list=False,
+                     repository_id=None, *args, **kwargs):
+        account = resources.hosting_service_account.get_object(
+            request, *args, **kwargs)
+
+        if is_list:
+            # Wrap the paginator in a LocalDataQuerySet, so that we can get
+            # to it later in RemoteRepositoryResponsePaginated.
+            lookup_kwargs = {}
+
+            for name in ('owner', 'owner-type', 'filter-type'):
+                if kwargs.get(name):
+                    arg = name.replace('-', '_')
+                    lookup_kwargs[arg] = kwargs[name]
+
+            result = account.service.get_remote_repositories(start=start,
+                                                             **lookup_kwargs)
+        else:
+            result = account.service.get_remote_repository(repository_id)
+
+        return LocalDataQuerySet([result])
+
+    def get_serializer_for_object(self, obj):
+        if isinstance(obj, RemoteRepository):
+            return self
+
+        return super(RemoteRepositoryResource, self).get_serializer_for_object(
+            obj)
+
+    @webapi_request_fields(
+        optional={
+            'owner': {
+                'type': six.text_type,
+                'description': 'The owner (user account or organization) '
+                               'to look up repositories for. Defaults to '
+                               'the owner of the hosting service account.',
+            },
+            'owner-type': {
+                'type': six.text_type,
+                'description': 'Indicates what sort of account the owner '
+                               'represents. This may be required by some '
+                               'services, and the values are dependent on '
+                               'that service.',
+            },
+            'filter-type': {
+                'type': six.text_type,
+                'description': 'Filters the list of results. Allowed values '
+                               'are dependent on the hosting service. '
+                               'Unexpected values will be ignored.',
+            },
+            'start': {
+                'type': int,
+                'description': 'The 0-based index of the first page of '
+                               'results to fetch.',
+            },
+        },
+        allow_unknown=True
+    )
+    @augment_method_from(WebAPIResource)
+    def get_list(self, request, *args, **kwargs):
+        """Returns the list of remote repositories on the hosting service.
+
+        Different hosting service backends have different criteria for
+        performing the lookups. Some hosting services have multiple types of
+        owners, specified by passing ``owner-type``. Filtering may also be
+        possible by passing ``filter-type``. Performing lookups requires
+        knowing the possible values for the service ahead of time and passing
+        the proper parameters in the query string.
+
+        Pagination works a bit differently for this resource than most.
+        Instead of ``?start=`` taking an index into the number of results,
+        this resource's ``?start=`` takes a 0-based index of the page of
+        results.
+
+        ``?max-results=`` and ``?counts-only=`` are not supported, as they're
+        not compatible with all hosting services.
+
+        Callers should always use the ``next`` and ``prev`` links for
+        navigation, and should not build page indexes themselves.
+        """
+        pass
+
+    @augment_method_from(WebAPIResource)
+    def get(self, *args, **kwargs):
+        """Provides information on a particular remote repository.
+
+        If the remote repository exists and is accessible by the linked
+        hosting service account (that of the parent resource), then the
+        details of that repository will be returned in the payload.
+
+        The ID expected for the lookup in the URL is specific to the type
+        of hosting service.
+        """
+        pass
+
+
+remote_repository_resource = RemoteRepositoryResource()
diff --git a/reviewboard/webapi/tests/mimetypes.py b/reviewboard/webapi/tests/mimetypes.py
index 77a83a6c37cd7b8bd08eb5b6b521ce296bb3636d..590e4bf64cc1cb89c1fab95a2e69d28c680c566c 100644
--- a/reviewboard/webapi/tests/mimetypes.py
+++ b/reviewboard/webapi/tests/mimetypes.py
@@ -56,6 +56,10 @@ hosting_service_account_item_mimetype = \
     _build_mimetype('hosting-service-account')
 
 
+remote_repository_list_mimetype = _build_mimetype('remote-repositories')
+remote_repository_item_mimetype = _build_mimetype('remote-repository')
+
+
 repository_list_mimetype = _build_mimetype('repositories')
 repository_item_mimetype = _build_mimetype('repository')
 
diff --git a/reviewboard/webapi/tests/test_remote_repository.py b/reviewboard/webapi/tests/test_remote_repository.py
new file mode 100644
index 0000000000000000000000000000000000000000..7f0aaa0e90c3649ea9736fa605c1b5fdf9ab917f
--- /dev/null
+++ b/reviewboard/webapi/tests/test_remote_repository.py
@@ -0,0 +1,159 @@
+from __future__ import unicode_literals
+
+import json
+
+from django.utils import six
+from kgb import SpyAgency
+
+from reviewboard.hostingsvcs.github import GitHub
+from reviewboard.hostingsvcs.models import HostingServiceAccount
+from reviewboard.hostingsvcs.repository import RemoteRepository
+from reviewboard.hostingsvcs.utils.paginator import APIPaginator
+from reviewboard.webapi.resources import resources
+from reviewboard.webapi.tests.base import BaseWebAPITestCase
+from reviewboard.webapi.tests.mimetypes import (
+    remote_repository_item_mimetype,
+    remote_repository_list_mimetype)
+from reviewboard.webapi.tests.mixins import BasicTestsMetaclass
+from reviewboard.webapi.tests.urls import (get_remote_repository_item_url,
+                                           get_remote_repository_list_url)
+
+
+def _compare_item(self, item_rsp, remote_repository):
+    self.assertEqual(item_rsp['id'], remote_repository.id)
+    self.assertEqual(item_rsp['name'], remote_repository.name)
+    self.assertEqual(item_rsp['owner'], remote_repository.owner)
+    self.assertEqual(item_rsp['scm_type'], remote_repository.scm_type)
+    self.assertEqual(item_rsp['path'], remote_repository.path)
+    self.assertEqual(item_rsp['mirror_path'], remote_repository.mirror_path)
+
+
+class RemoteRepositoryTestPaginator(APIPaginator):
+    def __init__(self, results):
+        self.results = results
+
+        super(RemoteRepositoryTestPaginator, self).__init__(client=None,
+                                                            url='')
+
+    def fetch_url(self, url):
+        return {
+            'data': self.results,
+        }
+
+
+@six.add_metaclass(BasicTestsMetaclass)
+class ResourceListTests(SpyAgency, BaseWebAPITestCase):
+    """Testing the RemoteRepositoryResource list APIs."""
+    fixtures = ['test_users']
+    sample_api_url = 'hosting-service-accounts/<id>/remote-repositories/'
+    resource = resources.remote_repository
+    basic_get_use_admin = True
+
+    compare_item = _compare_item
+
+    def setup_http_not_allowed_list_test(self, user):
+        account = HostingServiceAccount.objects.create(service_name='github',
+                                                       username='bob')
+
+        return get_remote_repository_list_url(account)
+
+    #
+    # HTTP GET tests
+    #
+
+    def setup_basic_get_test(self, user, with_local_site, local_site_name,
+                             populate_items):
+        account = HostingServiceAccount.objects.create(
+            service_name='github',
+            username='bob',
+            local_site=self.get_local_site_or_none(name=local_site_name),
+            data=json.dumps({
+                'authorization': {
+                    'token': '123',
+                },
+            }))
+
+        service = account.service
+
+        remote_repositories = [
+            RemoteRepository(service,
+                             repository_id='123',
+                             name='repo1',
+                             owner='bob',
+                             scm_type='Git',
+                             path='ssh://example.com/repo1',
+                             mirror_path='https://example.com/repo1'),
+            RemoteRepository(service,
+                             repository_id='456',
+                             name='repo2',
+                             owner='bob',
+                             scm_type='Git',
+                             path='ssh://example.com/repo2',
+                             mirror_path='https://example.com/repo2'),
+        ]
+
+        paginator = RemoteRepositoryTestPaginator(remote_repositories)
+
+        self.spy_on(GitHub.get_remote_repositories,
+                    call_fake=lambda *args, **kwargs: paginator)
+
+        return (get_remote_repository_list_url(account, local_site_name),
+                remote_repository_list_mimetype,
+                remote_repositories)
+
+
+@six.add_metaclass(BasicTestsMetaclass)
+class ResourceItemTests(SpyAgency, BaseWebAPITestCase):
+    """Testing the RemoteRepositoryResource item APIs."""
+    fixtures = ['test_users']
+    sample_api_url = 'hosting-service-accounts/<id>/remote-repositories/<id>/'
+    resource = resources.remote_repository
+    basic_get_use_admin = True
+
+    compare_item = _compare_item
+
+    def setup_http_not_allowed_item_test(self, user):
+        account = HostingServiceAccount.objects.create(service_name='github',
+                                                       username='bob')
+
+        remote_repository = RemoteRepository(
+            account.service,
+            repository_id='123',
+            name='repo1',
+            owner='bob',
+            scm_type='Git',
+            path='ssh://example.com/repo1')
+
+        return get_remote_repository_item_url(remote_repository)
+
+    #
+    # HTTP GET tests
+    #
+
+    def setup_basic_get_test(self, user, with_local_site, local_site_name):
+        account = HostingServiceAccount.objects.create(
+            service_name='github',
+            username='bob',
+            local_site=self.get_local_site_or_none(name=local_site_name),
+            data=json.dumps({
+                'authorization': {
+                    'token': '123',
+                },
+            }))
+
+        remote_repository = RemoteRepository(
+            account.service,
+            repository_id='123',
+            name='repo1',
+            owner='bob',
+            scm_type='Git',
+            path='ssh://example.com/repo1',
+            mirror_path='https://example.com/repo1')
+
+        self.spy_on(GitHub.get_remote_repository,
+                    call_fake=lambda *args, **kwargs: remote_repository)
+
+        return (get_remote_repository_item_url(remote_repository,
+                                               local_site_name),
+                remote_repository_item_mimetype,
+                remote_repository)
diff --git a/reviewboard/webapi/tests/urls.py b/reviewboard/webapi/tests/urls.py
index 86ecc742aeced77db1acc0e012b0563068806f32..e4df4b30785b410aace9134724dc2bc84d0451a2 100644
--- a/reviewboard/webapi/tests/urls.py
+++ b/reviewboard/webapi/tests/urls.py
@@ -250,6 +250,22 @@ def get_hosting_service_account_item_url(account_or_id, local_site_name=None):
 
 
 #
+# RemoteRepositoryResource
+#
+def get_remote_repository_list_url(account, local_site_name=None):
+    return resources.remote_repository.get_list_url(
+        local_site_name=local_site_name,
+        account_id=account.pk)
+
+
+def get_remote_repository_item_url(remote_repository, local_site_name=None):
+    return resources.remote_repository.get_item_url(
+        local_site_name=local_site_name,
+        account_id=remote_repository.hosting_service_account.pk,
+        repository_id=remote_repository.id)
+
+
+#
 # RepositoryResource
 #
 def get_repository_list_url(local_site_name=None):
