diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 6fe7f2b276ccffa634098ce2ec75a4a4b2aeb3e1..433086e2cba281389656edd7eda7bd0f90db9d23 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -1,3 +1,8 @@
+"""
+Module rendering
+"""
+
+import hashlib
 import json
 import logging
 import mimetypes
@@ -5,6 +10,7 @@ import mimetypes
 import static_replace
 import xblock.reference.plugins
 
+from collections import OrderedDict
 from functools import partial
 from requests.auth import HTTPBasicAuth
 import dogstats_wrapper as dog_stats_api
@@ -13,6 +19,8 @@ from opaque_keys import InvalidKeyError
 from django.conf import settings
 from django.contrib.auth.models import User
 from django.core.cache import cache
+from django.core.context_processors import csrf
+from django.core.exceptions import PermissionDenied
 from django.core.urlresolvers import reverse
 from django.http import Http404, HttpResponse
 from django.views.decorators.csrf import csrf_exempt
@@ -32,7 +40,7 @@ from student.models import anonymous_id_for_user, user_by_anonymous_id
 from xblock.core import XBlock
 from xblock.fields import Scope
 from xblock.runtime import KvsFieldData, KeyValueStore
-from xblock.exceptions import NoSuchHandlerError
+from xblock.exceptions import NoSuchHandlerError, NoSuchViewError
 from xblock.django.request import django_to_webob_request, webob_to_django_response
 from xmodule.error_module import ErrorDescriptor, NonStaffErrorDescriptor
 from xmodule.exceptions import NotFoundError, ProcessingError
@@ -781,7 +789,7 @@ def handle_xblock_callback_noauth(request, course_id, usage_id, handler, suffix=
     """
     request.user.known = False
 
-    return _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, request.user)
+    return _invoke_xblock_handler(request, course_id, usage_id, handler, suffix)
 
 
 def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None):
@@ -802,7 +810,7 @@ def handle_xblock_callback(request, course_id, usage_id, handler, suffix=None):
     if not request.user.is_authenticated():
         return HttpResponse('Unauthenticated', status=403)
 
-    return _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, request.user)
+    return _invoke_xblock_handler(request, course_id, usage_id, handler, suffix)
 
 
 def xblock_resource(request, block_type, uri):  # pylint: disable=unused-argument
@@ -822,31 +830,20 @@ def xblock_resource(request, block_type, uri):  # pylint: disable=unused-argumen
     return HttpResponse(content, mimetype=mimetype)
 
 
-def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
+def _get_module_by_usage_id(request, course_id, usage_id):
     """
-    Invoke an XBlock handler, either authenticated or not.
-
-    Arguments:
-        request (HttpRequest): the current request
-        course_id (str): A string of the form org/course/run
-        usage_id (str): A string of the form i4x://org/course/category/name@revision
-        handler (str): The name of the handler to invoke
-        suffix (str): The suffix to pass to the handler when invoked
-        user (User): The currently logged in user
+    Gets a module instance based on its `usage_id` in a course, for a given request/user
 
+    Returns (instance, tracking_context)
     """
+    user = request.user
+
     try:
         course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
         usage_key = course_id.make_usage_key_from_deprecated_string(unquote_slashes(usage_id))
     except InvalidKeyError:
         raise Http404("Invalid location")
 
-    # Check submitted files
-    files = request.FILES or {}
-    error_msg = _check_files_limits(files)
-    if error_msg:
-        return HttpResponse(json.dumps({'success': error_msg}))
-
     try:
         descriptor = modulestore().get_item(usage_key)
         descriptor_orig_usage_key, descriptor_orig_version = modulestore().get_block_original_usage(usage_key)
@@ -859,13 +856,13 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
         )
         raise Http404
 
-    tracking_context_name = 'module_callback_handler'
     tracking_context = {
         'module': {
             'display_name': descriptor.display_name_with_default,
             'usage_key': unicode(descriptor.location),
         }
     }
+
     # For blocks that are inherited from a content library, we add some additional metadata:
     if descriptor_orig_usage_key is not None:
         tracking_context['module']['original_usage_key'] = unicode(descriptor_orig_usage_key)
@@ -884,6 +881,30 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
         log.debug("No module %s for user %s -- access denied?", usage_key, user)
         raise Http404
 
+    return (instance, tracking_context)
+
+
+def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix):
+    """
+    Invoke an XBlock handler, either authenticated or not.
+
+    Arguments:
+        request (HttpRequest): the current request
+        course_id (str): A string of the form org/course/run
+        usage_id (str): A string of the form i4x://org/course/category/name@revision
+        handler (str): The name of the handler to invoke
+        suffix (str): The suffix to pass to the handler when invoked
+    """
+
+    # Check submitted files
+    files = request.FILES or {}
+    error_msg = _check_files_limits(files)
+    if error_msg:
+        return JsonResponse(object={'success': error_msg}, status=413)
+
+    instance, tracking_context = _get_module_by_usage_id(request, course_id, usage_id)
+
+    tracking_context_name = 'module_callback_handler'
     req = django_to_webob_request(request)
     try:
         with tracker.get_tracker().context(tracking_context_name, tracking_context):
@@ -912,6 +933,52 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
     return webob_to_django_response(resp)
 
 
+def hash_resource(resource):
+    """
+    Hash a :class:`xblock.fragment.FragmentResource
+    """
+    md5 = hashlib.md5()
+    for data in resource:
+        md5.update(repr(data))
+    return md5.hexdigest()
+
+
+def xblock_view(request, course_id, usage_id, view_name):
+    """
+    Returns the rendered view of a given XBlock, with related resources
+
+    Returns a json object containing two keys:
+        html: The rendered html of the view
+        resources: A list of tuples where the first element is the resource hash, and
+            the second is the resource description
+    """
+    if not settings.FEATURES.get('ENABLE_XBLOCK_VIEW_ENDPOINT', False):
+        log.warn("Attempt to use deactivated XBlock view endpoint -"
+                 " see FEATURES['ENABLE_XBLOCK_VIEW_ENDPOINT']")
+        raise Http404
+
+    if not request.user.is_authenticated():
+        raise PermissionDenied
+
+    instance, tracking_context = _get_module_by_usage_id(request, course_id, usage_id)
+
+    try:
+        fragment = instance.render(view_name, context=request.GET)
+    except NoSuchViewError:
+        log.exception("Attempt to render missing view on %s: %s", instance, view_name)
+        raise Http404
+
+    hashed_resources = OrderedDict()
+    for resource in fragment.resources:
+        hashed_resources[hash_resource(resource)] = resource
+
+    return JsonResponse({
+        'html': fragment.content,
+        'resources': hashed_resources.items(),
+        'csrf_token': str(csrf(request)['csrf_token']),
+    })
+
+
 def get_score_bucket(grade, max_grade):
     """
     Function to split arbitrary score ranges into 3 buckets.
diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py
index 2a12a8c3eafe342bc8b226c7c9b272400691d654..278a28a108d05819bf257c5f73b053721a4a0d67 100644
--- a/lms/djangoapps/courseware/tests/test_module_render.py
+++ b/lms/djangoapps/courseware/tests/test_module_render.py
@@ -16,6 +16,7 @@ from django.contrib.auth.models import AnonymousUser
 from mock import MagicMock, patch, Mock
 from opaque_keys.edx.keys import UsageKey, CourseKey
 from opaque_keys.edx.locations import SlashSeparatedCourseKey
+from courseware.module_render import hash_resource
 from xblock.field_data import FieldData
 from xblock.runtime import Runtime
 from xblock.fields import ScopeIds
@@ -246,6 +247,14 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase):
         render.get_module_for_descriptor(self.mock_user, request, descriptor, field_data_cache, self.toy_course.id)
         render.get_module_for_descriptor(self.mock_user, request, descriptor, field_data_cache, self.toy_course.id)
 
+    def test_hash_resource(self):
+        """
+        Ensure that the resource hasher works and does not fail on unicode,
+        decoded or otherwise.
+        """
+        resources = ['ASCII text', u'❄ I am a special snowflake.', "❄ So am I, but I didn't tell you."]
+        self.assertEqual(hash_resource(resources), 'a76e27c8e80ca3efd7ce743093aa59e0')
+
 
 class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
     """
@@ -316,7 +325,7 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
             json.dumps({
                 'success': 'Submission aborted! Maximum %d files may be submitted at once' %
                            settings.MAX_FILEUPLOADS_PER_INPUT
-            })
+            }, indent=2)
         )
 
     def test_too_large_file(self):
@@ -336,7 +345,7 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
             json.dumps({
                 'success': 'Submission aborted! Your file "%s" is too large (max size: %d MB)' %
                            (inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE / (1000 ** 2))
-            })
+            }, indent=2)
         )
 
     def test_xmodule_dispatch(self):
@@ -399,6 +408,29 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
                 'bad_dispatch',
             )
 
+    @patch.dict('django.conf.settings.FEATURES', {'ENABLE_XBLOCK_VIEW_ENDPOINT': True})
+    def test_xblock_view_handler(self):
+        args = [
+            'edX/toy/2012_Fall',
+            quote_slashes('i4x://edX/toy/videosequence/Toy_Videos'),
+            'student_view'
+        ]
+        xblock_view_url = reverse(
+            'xblock_view',
+            args=args
+        )
+
+        request = self.request_factory.get(xblock_view_url)
+        request.user = self.mock_user
+        response = render.xblock_view(request, *args)
+        self.assertEquals(200, response.status_code)
+
+        expected = ['csrf_token', 'html', 'resources']
+        content = json.loads(response.content)
+        for section in expected:
+            self.assertIn(section, content)
+        self.assertIn('<div class="xblock xblock-student_view xmodule_display', content['html'])
+
 
 @ddt.ddt
 class TestTOC(ModuleStoreTestCase):
diff --git a/lms/djangoapps/lms_xblock/runtime.py b/lms/djangoapps/lms_xblock/runtime.py
index 7b5c3fe7b95e995ef45dfaca2b1ddacfa8872a61..af0c32f14c5d815e7b7bef71185940f76726d4be 100644
--- a/lms/djangoapps/lms_xblock/runtime.py
+++ b/lms/djangoapps/lms_xblock/runtime.py
@@ -5,6 +5,7 @@ Module implementing `xblock.runtime.Runtime` functionality for the LMS
 import re
 import xblock.reference.plugins
 
+from django.conf import settings
 from django.core.urlresolvers import reverse
 from django.conf import settings
 from lms.djangoapps.lms_xblock.models import XBlockAsidesConfig
@@ -118,10 +119,11 @@ class LmsHandlerUrls(object):
         """
         local_resource_url for Studio
         """
-        return reverse('xblock_resource_url', kwargs={
+        path = reverse('xblock_resource_url', kwargs={
             'block_type': block.scope_ids.block_type,
             'uri': uri,
         })
+        return '//{}{}'.format(settings.SITE_NAME, path)
 
 
 class LmsPartitionService(PartitionService):
diff --git a/lms/envs/bok_choy.env.json b/lms/envs/bok_choy.env.json
index 7f60988d85cd8f2829851a3e54c0dc1c02d21a87..89223675c7501c3d9341dcb65e0133efaab4013f 100644
--- a/lms/envs/bok_choy.env.json
+++ b/lms/envs/bok_choy.env.json
@@ -104,7 +104,7 @@
     "SEGMENT_IO_LMS": true,
     "SERVER_EMAIL": "devops@example.com",
     "SESSION_COOKIE_DOMAIN": null,
-    "SITE_NAME": "localhost",
+    "SITE_NAME": "localhost:8003",
     "STATIC_ROOT_BASE": "** OVERRIDDEN **",
     "STATIC_URL_BASE": "/static/",
     "SYSLOG_SERVER": "",
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 64971438ffc68eefbc60b68132ae0b1d0b233040..9347fcc670ef4bd6144030ca6054824c3f5fe4ca 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -133,6 +133,13 @@ FEATURES = {
     # Toggles OAuth2 authentication provider
     'ENABLE_OAUTH2_PROVIDER': False,
 
+    # Allows to enable an API endpoint to serve XBlock view, used for example by external applications.
+    # See jquey-xblock: https://github.com/edx-solutions/jquery-xblock
+    'ENABLE_XBLOCK_VIEW_ENDPOINT': False,
+
+    # Allows to configure the LMS to provide CORS headers to serve requests from other domains
+    'ENABLE_CORS_HEADERS': False,
+
     # Can be turned off if course lists need to be hidden. Effects views and templates.
     'COURSES_ARE_BROWSABLE': True,
 
diff --git a/lms/urls.py b/lms/urls.py
index a3b604d756da4db0f5c797e978e1132d5818acad..37f197aed898f178dbdbb6fa4a7ebcefdef864bf 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -237,6 +237,11 @@ if settings.COURSEWARE_ENABLED:
         url(r'^courses/{course_key}/xblock/{usage_key}/handler/(?P<handler>[^/]*)(?:/(?P<suffix>.*))?$'.format(course_key=settings.COURSE_ID_PATTERN, usage_key=settings.USAGE_ID_PATTERN),
             'courseware.module_render.handle_xblock_callback',
             name='xblock_handler'),
+        url(r'^courses/{course_key}/xblock/{usage_key}/view/(?P<view_name>[^/]*)$'.format(
+            course_key=settings.COURSE_ID_PATTERN,
+            usage_key=settings.USAGE_ID_PATTERN),
+            'courseware.module_render.xblock_view',
+            name='xblock_view'),
         url(r'^courses/{course_key}/xblock/{usage_key}/handler_noauth/(?P<handler>[^/]*)(?:/(?P<suffix>.*))?$'.format(course_key=settings.COURSE_ID_PATTERN, usage_key=settings.USAGE_ID_PATTERN),
             'courseware.module_render.handle_xblock_callback_noauth',
             name='xblock_handler_noauth'),