diff --git a/README.md b/README.md
index f2158298..a3a77830 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,11 @@
 
 The [Topcoder](https://www.topcoder.com) lib for internal ReactJS projects.
 
+
+### Configuration for AV-Scan scorer review type ID
+
+Change the property in `AV_SCAN_SCORER_REVIEW_TYPE_ID` in config.
+
 ### Development
 ```shell
 # Install dependencies
diff --git a/__tests__/__snapshots__/index.js.snap b/__tests__/__snapshots__/index.js.snap
index e7ee51bc..fd8bd27a 100644
--- a/__tests__/__snapshots__/index.js.snap
+++ b/__tests__/__snapshots__/index.js.snap
@@ -17,6 +17,8 @@ Object {
       "getActiveChallengesCountInit": [Function],
       "getDetailsDone": [Function],
       "getDetailsInit": [Function],
+      "getMmSubmissionsDone": [Function],
+      "getMmSubmissionsInit": [Function],
       "getSubmissionsDone": [Function],
       "getSubmissionsInit": [Function],
       "loadResultsDone": [Function],
@@ -426,14 +428,17 @@ Object {
     "countReset": [Function],
     "debug": [Function],
     "dir": [Function],
+    "dirxml": [Function],
     "error": [Function],
     "group": [Function],
     "groupCollapsed": [Function],
     "groupEnd": [Function],
     "info": [Function],
     "log": [Function],
+    "table": [Function],
     "time": [Function],
     "timeEnd": [Function],
+    "timeLog": [Function],
     "trace": [Function],
     "warn": [Function],
   },
@@ -467,6 +472,7 @@ Object {
       "getApiV2": [Function],
       "getApiV3": [Function],
       "getApiV4": [Function],
+      "getApiV5": [Function],
       "getTcM2mToken": [Function],
     },
     "billing": Object {
@@ -510,6 +516,10 @@ Object {
       "default": undefined,
       "getReviewOpportunitiesService": [Function],
     },
+    "submissions": Object {
+      "default": undefined,
+      "getService": [Function],
+    },
     "terms": Object {
       "default": undefined,
       "getService": [Function],
@@ -527,6 +537,12 @@ Object {
       "getService": [Function],
     },
   },
+  "submission": Object {
+    "default": undefined,
+    "getFinalScore": [Function],
+    "getProvisionalScore": [Function],
+    "processMMSubmissions": [Function],
+  },
   "tc": Object {
     "COMPETITION_TRACKS": Object {
       "DATA_SCIENCE": "data_science",
diff --git a/__tests__/reducers/__snapshots__/challenge.js.snap b/__tests__/reducers/__snapshots__/challenge.js.snap
index 9db7c531..7ddb822b 100644
--- a/__tests__/reducers/__snapshots__/challenge.js.snap
+++ b/__tests__/reducers/__snapshots__/challenge.js.snap
@@ -6,7 +6,9 @@ Object {
   "details": null,
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {},
   "mySubmissionsManagement": Object {},
   "registering": false,
@@ -27,7 +29,9 @@ Object {
   "fetchChallengeFailure": false,
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {},
   "mySubmissionsManagement": Object {},
   "registering": false,
@@ -48,7 +52,9 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {},
   "mySubmissionsManagement": Object {},
   "registering": false,
@@ -66,7 +72,9 @@ Object {
   "fetchChallengeFailure": false,
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "12345",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {},
   "mySubmissionsManagement": Object {},
   "registering": false,
@@ -87,8 +95,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "12345",
     "v2": Array [],
@@ -114,8 +124,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "12345",
     "v2": Array [
@@ -143,8 +155,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "",
     "v2": null,
@@ -170,8 +184,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "12345",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "",
     "v2": null,
@@ -196,8 +212,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "",
     "v2": null,
@@ -223,8 +241,10 @@ Object {
   "fetchChallengeFailure": false,
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "12345",
     "v2": Array [
@@ -252,8 +272,10 @@ Object {
   "fetchChallengeFailure": false,
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "12345",
     "v2": Array [
@@ -281,8 +303,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "12345",
     "v2": Array [
@@ -310,8 +334,10 @@ Object {
   "fetchChallengeFailure": false,
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "12345",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "12345",
     "v2": Array [
@@ -339,8 +365,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "12345",
     "v2": Array [],
@@ -366,8 +394,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "12345",
     "v2": Array [
@@ -395,8 +425,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "",
     "v2": null,
@@ -422,8 +454,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "12345",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "",
     "v2": null,
@@ -448,8 +482,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "",
     "v2": null,
@@ -471,7 +507,9 @@ Object {
   "details": null,
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {},
   "mySubmissionsManagement": Object {},
   "registering": false,
@@ -492,7 +530,9 @@ Object {
   "fetchChallengeFailure": false,
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {},
   "mySubmissionsManagement": Object {},
   "registering": false,
@@ -513,7 +553,9 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {},
   "mySubmissionsManagement": Object {},
   "registering": false,
@@ -531,7 +573,9 @@ Object {
   "fetchChallengeFailure": false,
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "12345",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {},
   "mySubmissionsManagement": Object {},
   "registering": false,
@@ -552,8 +596,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "12345",
     "v2": Array [],
@@ -579,8 +625,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "12345",
     "v2": Array [
@@ -608,8 +656,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "",
     "v2": null,
@@ -635,8 +685,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "12345",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "",
     "v2": null,
@@ -661,8 +713,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "",
     "v2": null,
@@ -684,7 +738,9 @@ Object {
   "details": null,
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {},
   "mySubmissionsManagement": Object {},
   "registering": false,
@@ -705,7 +761,9 @@ Object {
   "fetchChallengeFailure": false,
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {},
   "mySubmissionsManagement": Object {},
   "registering": false,
@@ -726,7 +784,9 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {},
   "mySubmissionsManagement": Object {},
   "registering": false,
@@ -744,7 +804,9 @@ Object {
   "fetchChallengeFailure": false,
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "12345",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {},
   "mySubmissionsManagement": Object {},
   "registering": false,
@@ -765,8 +827,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "12345",
     "v2": Array [],
@@ -792,8 +856,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "12345",
     "v2": Array [
@@ -821,8 +887,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "",
     "v2": null,
@@ -848,8 +916,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "12345",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "",
     "v2": null,
@@ -874,8 +944,10 @@ Object {
   "fetchChallengeFailure": "Unknown error",
   "loadingCheckpoints": false,
   "loadingDetailsForChallengeId": "",
+  "loadingMMSubmissionsForChallengeId": "",
   "loadingResultsForChallengeId": "",
   "loadingSubmissionsForChallengeId": "",
+  "mmSubmissions": Array [],
   "mySubmissions": Object {
     "challengeId": "",
     "v2": null,
diff --git a/config/default.json b/config/default.json
new file mode 100644
index 00000000..23136c0f
--- /dev/null
+++ b/config/default.json
@@ -0,0 +1,5 @@
+{
+  "AV_SCAN_SCORER_REVIEW_TYPE_ID": "",
+  "PAGE_SIZE": 50,
+  "REVIEW_OPPORTUNITY_PAGE_SIZE": 1000
+}
diff --git a/config/development.json b/config/development.json
new file mode 100644
index 00000000..d7c751d4
--- /dev/null
+++ b/config/development.json
@@ -0,0 +1,3 @@
+{
+  "AV_SCAN_SCORER_REVIEW_TYPE_ID": ""
+}
diff --git a/config/jest/default.js b/config/jest/default.js
index 9479649f..aa792822 100644
--- a/config/jest/default.js
+++ b/config/jest/default.js
@@ -1,3 +1,9 @@
 const config = require('topcoder-react-utils/config/jest/default');
+const nodeConfig = require('config');
 
-module.exports = config;
+module.exports = {
+  ...config,
+  globals: {
+    CONFIG: nodeConfig,
+  },
+};
diff --git a/config/production.json b/config/production.json
new file mode 100644
index 00000000..d7c751d4
--- /dev/null
+++ b/config/production.json
@@ -0,0 +1,3 @@
+{
+  "AV_SCAN_SCORER_REVIEW_TYPE_ID": ""
+}
diff --git a/config/webpack/default.js b/config/webpack/default.js
index 932840d6..a1a3c63b 100644
--- a/config/webpack/default.js
+++ b/config/webpack/default.js
@@ -1,7 +1,15 @@
+// eslint-disable-next-line import/no-extraneous-dependencies
+const webpack = require('webpack');
+
 module.exports = {
+  plugins: [
+    // eslint-disable-next-line global-require
+    new webpack.DefinePlugin({ CONFIG: JSON.stringify(require('config')) }),
+  ],
   // Don't include the dependencies to keep built bundle small,
   // they will be provided by the app using this lib
   externals: [
+    'config',
     'qs',
     'lodash',
     'le_node',
@@ -17,4 +25,7 @@ module.exports = {
     'to-capital-case',
     'topcoder-react-utils',
   ],
+  node: {
+    fs: 'empty',
+  },
 };
diff --git a/docs/actions.challenge.md b/docs/actions.challenge.md
index 23d16fb5..015a2abd 100644
--- a/docs/actions.challenge.md
+++ b/docs/actions.challenge.md
@@ -24,6 +24,8 @@ Actions related to Topcoder challenges APIs.
     * [.updateChallengeDone(uuid, challenge, tokenV3)](#module_actions.challenge.updateChallengeDone) ⇒ <code>Action</code>
     * [.getActiveChallengesCountInit()](#module_actions.challenge.getActiveChallengesCountInit) ⇒ <code>Action</code>
     * [.getActiveChallengesCountDone(handle, tokenV3)](#module_actions.challenge.getActiveChallengesCountDone) ⇒ <code>Action</code>
+    * [.getMMSubmissionsInit(challengeId)](#module_actions.challenge.getMMSubmissionsInit) ⇒ <code>Action</code>
+    * [.getMMSubmissionsDone(challengeId, submitterIds, registrants, tokenV3)](#module_actions.challenge.getMMSubmissionsDone) ⇒ <code>Action</code>
 
 <a name="module_actions.challenge.dropCheckpoints"></a>
 
@@ -31,14 +33,14 @@ Actions related to Topcoder challenges APIs.
 Creates an action that drops from Redux store all checkpoints loaded
  before.
 
-**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)  
+**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)
 <a name="module_actions.challenge.dropResults"></a>
 
 ### actions.challenge.dropResults() ⇒ <code>Action</code>
 Creates an action that drops from Redux store all challenge results
  loaded before.
 
-**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)  
+**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)
 <a name="module_actions.challenge.getDetailsInit"></a>
 
 ### actions.challenge.getDetailsInit(challengeId) ⇒ <code>Action</code>
@@ -93,7 +95,7 @@ challenge.
 Creates an action that signals beginning of registration for a
 challenge.
 
-**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)  
+**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)
 <a name="module_actions.challenge.registerDone"></a>
 
 ### actions.challenge.registerDone(auth, challengeId) ⇒ <code>Action</code>
@@ -114,7 +116,7 @@ Creates an action that registers user for a challenge.
 Creates an action that signals beginning of user unregistration from a
  challenge.
 
-**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)  
+**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)
 <a name="module_actions.challenge.unregisterDone"></a>
 
 ### actions.challenge.unregisterDone(auth, challengeId) ⇒ <code>Action</code>
@@ -161,7 +163,7 @@ Creates an action that loads challenge results.
 Creates an action that signals beginning of challenge checkpoints data
  loading.
 
-**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)  
+**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)
 <a name="module_actions.challenge.fetchCheckpointsDone"></a>
 
 ### actions.challenge.fetchCheckpointsDone(tokenV2, challengeId)
@@ -180,7 +182,7 @@ Creates an action that loads challenge checkpoints data.
 Creates an action that Toggles checkpoint details panel in the Topcoder
  Submission Management Page.
 
-**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)  
+**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)
 **Todo**
 
 - [ ] This is UI action relevant to a specific page in specific app. Must be
@@ -197,7 +199,7 @@ Creates an action that Toggles checkpoint details panel in the Topcoder
 ### actions.challenge.updateChallengeInit(uuid) ⇒ <code>Action</code>
 Creates an action that signals beginning of challenge details update.
 
-**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)  
+**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)
 **Todo**
 
 - [ ] No idea, why we have this action. This functionality should be covered
@@ -214,7 +216,7 @@ Creates an action that signals beginning of challenge details update.
 ### actions.challenge.updateChallengeDone(uuid, challenge, tokenV3) ⇒ <code>Action</code>
 Creates an action that updates challenge details.
 
-**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)  
+**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)
 **Todo**
 
 - [ ] No idea, why we have this action. This functionality should be covered
@@ -233,7 +235,7 @@ Creates an action that updates challenge details.
 ### actions.challenge.getActiveChallengesCountInit() ⇒ <code>Action</code>
 Creates an action that signals beginning of getting count of user's active challenges.
 
-**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)  
+**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)
 <a name="module_actions.challenge.getActiveChallengesCountDone"></a>
 
 ### actions.challenge.getActiveChallengesCountDone(handle, tokenV3) ⇒ <code>Action</code>
@@ -246,3 +248,28 @@ Creates an action that gets count of user's active challenges from the backend.
 | handle | <code>String</code> | Topcoder user handle. |
 | tokenV3 | <code>String</code> | Optional. Topcoder auth token v3. Without token only  public challenges will be counted. With the token provided, the action will  also count private challenges related to this user. |
 
+
+
+### actions.challenge.getMMSubmissionsInit(challengeId) ⇒ <code>Action</code>
+Creates an action that signals beginning of Marathon Match submissions loading.
+
+**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)
+<a name="module_actions.challenge.getMMSubmissionsInit"></a>
+
+| Param | Type | Description |
+| --- | --- | --- |
+| challengeId | <code>String</code> | The challenge id. |
+
+### actions.challenge.getMMSubmissionsDone(challengeId, submitterIds, registrants, tokenV3) ⇒ <code>Action</code>
+Creates an action that loads Marathon Match submissions to the specified
+challenge.
+
+**Kind**: static method of [<code>actions.challenge</code>](#module_actions.challenge)
+<a name="module_actions.challenge.getMMSubmissionsDone"></a>
+
+| Param | Type | Description |
+| --- | --- | --- |
+| challengeId | <code>String</code> | The challenge id. |
+| submitterIds | <code>Array</code> | The ids of submitters |
+| registrants | <code>Array</code> | The registrants of challenge |
+| tokenV3 | <code>String</code> | Topcoder auth token v3. |
diff --git a/docs/index.md b/docs/index.md
index 185c46d5..9139f14a 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -143,6 +143,12 @@ action to really cancel group loading.</p>
 <dd><p>Reducer for state.terms.</p>
 </dd>
 <dt>
+<a href="services.submissions.md">services.submissions</a></dt>
+<dd><p>his module provides a service for convenient manipulation with
+ Topcoder submissions via TC API.</p>
+</dd>
+<dt>
+<dt>
 <a href="services.api.md">services.api</a></dt>
 <dd><p>This module provides a service for conventient access to Topcoder APIs.</p>
 </dd>
@@ -301,3 +307,8 @@ the proxy will forward them to the service only if LOG_ENTRIES_TOKEN is set).</p
 <dd><p>Collection of url function.</p>
 </dd>
 <dt>
+ <dt>
+<a href="submission.md">submission</a></dt>
+<dd><p>Collection of submission function.</p>
+</dd>
+<dt>
diff --git a/docs/reducers.challenge.md b/docs/reducers.challenge.md
index d2f2bd0e..eb3514f6 100644
--- a/docs/reducers.challenge.md
+++ b/docs/reducers.challenge.md
@@ -15,6 +15,8 @@ State segment managed by this reducer has the following strcuture:
         * [.default](#module_reducers.challenge.default)
         * [.factory(options)](#module_reducers.challenge.factory) ⇒ <code>Promise</code>
     * _inner_
+        * [~onGetMMSubmissionsInit(state, action)](#module_reducers.challenge..onGetMMSubmissionsInit) ⇒ <code>Object</code>
+        * [~onGetMMSubmissionsDone(state, action)](#module_reducers.challenge..onGetDetailsDone) ⇒ <code>Object</code>
         * [~onGetDetailsInit(state, action)](#module_reducers.challenge..onGetDetailsInit) ⇒ <code>Object</code>
         * [~onGetDetailsDone(state, action)](#module_reducers.challenge..onGetDetailsDone) ⇒ <code>Object</code>
         * [~onGetSubmissionsInit(state, action)](#module_reducers.challenge..onGetSubmissionsInit) ⇒ <code>Object</code>
@@ -34,7 +36,7 @@ State segment managed by this reducer has the following strcuture:
 ### reducers.challenge.default
 Reducer with default intial state.
 
-**Kind**: static property of [<code>reducers.challenge</code>](#module_reducers.challenge)  
+**Kind**: static property of [<code>reducers.challenge</code>](#module_reducers.challenge)
 <a name="module_reducers.challenge.factory"></a>
 
 ### reducers.challenge.factory(options) ⇒ <code>Promise</code>
@@ -42,7 +44,7 @@ Factory which creates a new reducer with its initial state tailored to the
 given options object, if specified (for server-side rendering). If options
 object is not specified, it creates just the default reducer. Accepted options are:
 
-**Kind**: static method of [<code>reducers.challenge</code>](#module_reducers.challenge)  
+**Kind**: static method of [<code>reducers.challenge</code>](#module_reducers.challenge)
 **Resolves**: <code>Function(state, action): state</code> New reducer.  
 
 | Param | Type | Default | Description |
@@ -53,18 +55,48 @@ object is not specified, it creates just the default reducer. Accepted options a
 | [options.challenge.challengeDetails.id] | <code>String</code> | <code>&#x27;&#x27;</code> | Optional. ID of  the challenge to load details for. |
 | [options.challenge.challengeDetails.mySubmission] | <code>Boolean</code> | <code>false</code> | Optional. The flag indicates whether load my submission. |
 
+<a name="module_reducers.challenge..onGetMMSubmissionsInit"></a>
+
+### reducers.challenge~onGetMMSubmissionsInit(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE/GET_MM_SUBMISSION_INIT action.
+
+**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)
+**Returns**: <code>Object</code> - New state
+
+| Param | Type |
+| --- | --- |
+| state | <code>Object</code> |
+| action | <code>Object</code> |
+
+<a name="module_reducers.challenge..onGetDetailsDone"></a>
+
+### reducers.challenge~onGetMMSubmissionsDone(state, action) ⇒ <code>Object</code>
+Handles CHALLENGE/GET_MM_SUBMISSION_DONE action.
+Note, that it silently discards received details if the ID of received
+challenge mismatches the one stored in loadingMMSubmissionsForChallengeId field
+of the state.
+
+**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)
+**Returns**: <code>Object</code> - New state.
+
+| Param | Type |
+| --- | --- |
+| state | <code>Object</code> |
+| action | <code>Object</code> |
+
+
 <a name="module_reducers.challenge..onGetDetailsInit"></a>
 
 ### reducers.challenge~onGetDetailsInit(state, action) ⇒ <code>Object</code>
 Handles CHALLENGE/GET_DETAILS_INIT action.
 
-**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)  
+**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)
 **Returns**: <code>Object</code> - New state  
 
 | Param | Type |
 | --- | --- |
-| state | <code>Object</code> | 
-| action | <code>Object</code> | 
+| state | <code>Object</code> |
+| action | <code>Object</code> |
 
 <a name="module_reducers.challenge..onGetDetailsDone"></a>
 
@@ -74,26 +106,26 @@ Note, that it silently discards received details if the ID of received
 challenge mismatches the one stored in loadingDetailsForChallengeId field
 of the state.
 
-**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)  
+**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)
 **Returns**: <code>Object</code> - New state.  
 
 | Param | Type |
 | --- | --- |
-| state | <code>Object</code> | 
-| action | <code>Object</code> | 
+| state | <code>Object</code> |
+| action | <code>Object</code> |
 
 <a name="module_reducers.challenge..onGetSubmissionsInit"></a>
 
 ### reducers.challenge~onGetSubmissionsInit(state, action) ⇒ <code>Object</code>
 Handles CHALLENGE/GET_SUBMISSION_INIT action.
 
-**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)  
+**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)
 **Returns**: <code>Object</code> - New state.  
 
 | Param | Type |
 | --- | --- |
-| state | <code>Object</code> | 
-| action | <code>Object</code> | 
+| state | <code>Object</code> |
+| action | <code>Object</code>|
 
 <a name="module_reducers.challenge..onGetSubmissionsDone"></a>
 
@@ -128,8 +160,8 @@ Handles CHALLENGE/LOAD_RESULTS_INIT action.
 
 | Param | Type |
 | --- | --- |
-| state | <code>Object</code> | 
-| action | <code>Object</code> | 
+| state | <code>Object</code> |
+| action | <code>Object</code> |
 
 <a name="module_reducers.challenge..onLoadResultsDone"></a>
 
@@ -141,7 +173,7 @@ Handles CHALLENGE/LOAD_RESULTS_DONE action.
 | Param | Type |
 | --- | --- |
 | state | <code>Object</code> | 
-| action | <code>Object</code> | 
+| action | <code>Object</code> |
 
 <a name="module_reducers.challenge..onRegisterDone"></a>
 
@@ -164,15 +196,15 @@ Handles CHALLENGE/UNREGISTER_DONE action.
 
 | Param | Type |
 | --- | --- |
-| state | <code>Object</code> | 
-| action | <code>Object</code> | 
+| state | <code>Object</code> |
+| action | <code>Object</code> |
 
 <a name="module_reducers.challenge..onUpdateChallengeInit"></a>
 
 ### reducers.challenge~onUpdateChallengeInit(state, actions) ⇒ <code>Object</code>
 Handles CHALLENGE/UPDATE_CHALLENGE_INIT.
 
-**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)  
+**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)
 **Returns**: <code>Object</code> - New state.  
 
 | Param | Type | Description |
@@ -185,7 +217,7 @@ Handles CHALLENGE/UPDATE_CHALLENGE_INIT.
 ### reducers.challenge~onUpdateChallengeDone(state, actions) ⇒ <code>Object</code>
 Handles CHALLENGE/UPDATE_CHALLENGE_DONE.
 
-**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)  
+**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)
 **Returns**: <code>Object</code> - New state.  
 
 | Param | Type | Description |
@@ -198,7 +230,7 @@ Handles CHALLENGE/UPDATE_CHALLENGE_DONE.
 ### reducers.challenge~onGetActiveChallengesCountDone(state, action) ⇒ <code>Object</code>
 Handles CHALLENGE/GET_ACTIVE_CHALLENGES_COUNT_DONE action.
 
-**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)  
+**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)
 **Returns**: <code>Object</code> - New state  
 
 | Param | Type | Description |
@@ -211,7 +243,7 @@ Handles CHALLENGE/GET_ACTIVE_CHALLENGES_COUNT_DONE action.
 ### reducers.challenge~create(initialState) ⇒ <code>function</code>
 Creates a new Challenge reducer with the specified initial state.
 
-**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)  
+**Kind**: inner method of [<code>reducers.challenge</code>](#module_reducers.challenge)
 **Returns**: <code>function</code> - Challenge reducer.  
 
 | Param | Type | Description |
diff --git a/docs/services.submissions.md b/docs/services.submissions.md
new file mode 100644
index 00000000..201297e5
--- /dev/null
+++ b/docs/services.submissions.md
@@ -0,0 +1,57 @@
+<a name="module_services.submissions"></a>
+
+## services.submissions
+This module provides a service for searching for Topcoder
+submissions via API V5.
+
+* [services.submissions](#module_services.submissions)
+    * _static_
+        * [.getService(tokenV3)](#module_services.submissions.getService) ⇒ <code>SubmissionService</code>
+    * _inner_
+        * [~SubmissionsService](#module_services.submissions..SubmissionsService)
+            * [new SubmissionsService(tokenV3)](#new_module_services.submissions..SubmissionsService_new)
+            * [.getSubmissions(filters, params)](#module_services.submissions..SubmissionsService+getSubmissions) ⇒ <code>Promise</code>
+
+<a name="module_services.submissions.getService"></a>
+
+### services.submissions.getService(tokenV3) ⇒ <code>SubmissionsService</code>
+Returns a new or existing submissions service.
+
+**Kind**: static method of [<code>services.submissions</code>](#module_services.submissions)
+**Returns**: <code>SubmissionsService</code> - Submissions service object
+
+| Param | Type | Description |
+| --- | --- | --- |
+| tokenV3 | <code>String</code> | Auth token for Topcoder API v5. |
+
+<a name="module_services.submissions..SubmissionsService"></a>
+
+### services.submissions~SubmissionsService
+Service class.
+
+**Kind**: inner class of [<code>services.submissions</code>](#module_services.submissions)
+
+* [~SubmissionsService](#module_services.submissions..SubmissionsService)
+    * [new SubmissionsService(tokenV3)](#new_module_services.submissions..SubmissionsService_new)
+    * [.getSubmissions(filters, params)](#module_services.submissions..SubmissionsService+getSubmissions) ⇒ <code>Promise</code>
+
+<a name="new_module_services.submissions..SubmissionsService_new"></a>
+
+#### new SubmissionsService(tokenV3)
+
+| Param | Type | Description |
+| --- | --- | --- |
+| tokenV3 | <code>String</code> | Auth token for Topcoder API v5. |
+
+<a name="module_services.submissions..SubmissionsService+getSubmissions"></a>
+
+#### submissionsService.getSubmissions(filters, params) ⇒ <code>Promise</code>
+Get submissions of challenge
+
+**Kind**: instance method of [<code>SubmissionsService</code>](#module_services.submissions..SubmissionsService)
+**Returns**: <code>Promise</code> - Resolves to the submissions array.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| filters | <code>Object</code> | The filters object |
+| params | <code>Object</code> | The params object |
diff --git a/docs/submission.md b/docs/submission.md
new file mode 100644
index 00000000..ff48298b
--- /dev/null
+++ b/docs/submission.md
@@ -0,0 +1,38 @@
+<a name="module_submission"></a>
+
+## submission
+Collection of submission functions.
+
+* [submission](#module_submission)
+    * [.getProvisionalScore(submission)](#module_submission.getProvisionalScore)
+    * [.getFinalScore(submission)](#module_submission.getFinalScore)
+    * [.processMMSubmissions(submissions, resources, registrants)](#module_submission.processMMSubmissions)
+
+<a name="module_submission.getProvisionalScore"></a>
+### submission.getProvisionalScore(submission)
+Get provisional score of submission
+**Kind**: static method of [<code>submission</code>](#module_submission)
+
+| Param | Type |
+| --- | --- |
+| submission | <code>Object</code> |
+
+<a name="module_submission.getFinalScore"></a>
+### submission.getFinalScore(submission)
+Get final score of submission
+**Kind**: static method of [<code>submission</code>](#module_submission)
+
+| Param | Type |
+| --- | --- |
+| submission | <code>Object</code> |
+
+<a name="module_submission.processMMSubmissions"></a>
+### submission.processMMSubmissions(submissions, resources, registrants)
+Process submissions of MM challenge
+**Kind**: static method of [<code>submission</code>](#module_submission)
+
+| Param | Type |
+| --- | --- |
+| submission | <code>Object</code> |
+| resources | <code>Array</code> |
+| registrants | <code>Array</code> |
diff --git a/package.json b/package.json
index a8163f0b..30247526 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
   "version": "0.7.15",
   "dependencies": {
     "auth0-js": "^6.8.4",
+    "config": "^3.1.0",
     "isomorphic-fetch": "^2.2.1",
     "le_node": "^1.7.0",
     "lodash": "^4.17.10",
diff --git a/src/actions/challenge-listing.js b/src/actions/challenge-listing.js
index e60f10f0..1ab0d8f5 100644
--- a/src/actions/challenge-listing.js
+++ b/src/actions/challenge-listing.js
@@ -1,7 +1,7 @@
 /**
  * Challenge listing actions.
  */
-
+/* global CONFIG */
 import _ from 'lodash';
 import { createActions } from 'redux-actions';
 import { decodeToken } from 'tc-accounts';
@@ -10,12 +10,12 @@ import { processSRM, COMPETITION_TRACKS } from '../utils/tc';
 import { services } from '../services';
 import { errors } from '../utils';
 import * as filterUtil from '../utils/challenge/filter';
-import * as config from '../config';
+
 
 const { fireErrorMessage } = errors;
 const { getService } = services.challenge;
 const { getReviewOpportunitiesService } = services.reviewOpportunities;
-const { PAGE_SIZE, REVIEW_OPPORTUNITY_PAGE_SIZE } = config;
+const { PAGE_SIZE, REVIEW_OPPORTUNITY_PAGE_SIZE } = CONFIG;
 
 /**
  * Process filter
diff --git a/src/actions/challenge.js b/src/actions/challenge.js
index f5dfb09b..9c00f33f 100644
--- a/src/actions/challenge.js
+++ b/src/actions/challenge.js
@@ -2,12 +2,48 @@
  * @module "actions.challenge"
  * @desc Actions related to Topcoder challenges APIs.
  */
-
+/* global CONFIG */
 import _ from 'lodash';
 import { config } from 'topcoder-react-utils';
 import { createActions } from 'redux-actions';
 import { getService as getChallengesService } from '../services/challenges';
+import { getService as getSubmissionService } from '../services/submissions';
+import { getService as getMemberService } from '../services/members';
 import { getApi } from '../services/api';
+import * as submissionUtil from '../utils/submission';
+
+const { PAGE_SIZE } = CONFIG;
+
+/**
+ * Private. Loads from the backend all data matching some conditions.
+ * @param {Function} getter Given params object of shape { limit, offset }
+ *  loads from the backend at most "limit" data, skipping the first
+ *  "offset" ones. Returns loaded data as an array.
+ * @param {Number} page Optional. Next page of data to load.
+ * @param {Array} prev Optional. data loaded so far.
+ */
+function getAll(getter, page = 1, prev) {
+  /* Amount of submissions to fetch in one API call. 50 is the current maximum
+   * amount of submissions the backend returns, event when the larger limit is
+   * explicitely required. */
+  return getter({
+    page,
+    perPage: PAGE_SIZE,
+  }).then((res) => {
+    if (res.length === 0) {
+      return prev || res;
+    }
+    // parse submissions
+    let current = [];
+    if (prev) {
+      current = prev.concat(res);
+    } else {
+      current = res;
+    }
+    return getAll(getter, 1 + page, current);
+  });
+}
+
 
 /**
  * @static
@@ -83,6 +119,49 @@ function getSubmissionsDone(challengeId, tokenV2) {
     });
 }
 
+/**
+ * @static
+ * @desc Creates an action that signals beginning of Marathon Match submissions loading.
+ * @param {String} challengeId Challenge ID.
+ * @return {Action}
+ */
+function getMMSubmissionsInit(challengeId) {
+  /* As a safeguard, we enforce challengeId to be string (in case somebody
+   * passes in a number, by mistake). */
+  return _.toString(challengeId);
+}
+
+
+/**
+ * @static
+ * @desc Creates an action that loads Marathon Match submissions to the specified
+ * challenge.
+ * @param {String} challengeId Challenge ID.
+ * @param {Array} submitterIds The array of submitter ids.
+ * @param {Array} registrants The array of register.
+ * @param {String} tokenV3  Topcoder auth token v3.
+ * @return {Action}
+ */
+function getMMSubmissionsDone(challengeId, submitterIds, registrants, tokenV3) {
+  const filter = { challengeId };
+  const memberService = getMemberService(tokenV3);
+  const submissionsService = getSubmissionService(tokenV3);
+  const calls = [
+    memberService.getMembersInformation(submitterIds),
+    getAll(params => submissionsService.getSubmissions(filter, params)),
+  ];
+  return Promise.all(calls).then(([resources, submissions]) => {
+    const finalSubmissions = submissionUtil
+      .processMMSubmissions(submissions, resources, registrants);
+    return {
+      challengeId,
+      submissions: finalSubmissions,
+      tokenV3,
+    };
+  });
+}
+
+
 /**
  * @static
  * @desc Creates an action that signals beginning of registration for a
@@ -305,5 +384,7 @@ export default createActions({
     UPDATE_CHALLENGE_DONE: updateChallengeDone,
     GET_ACTIVE_CHALLENGES_COUNT_INIT: getActiveChallengesCountInit,
     GET_ACTIVE_CHALLENGES_COUNT_DONE: getActiveChallengesCountDone,
+    GET_MM_SUBMISSIONS_INIT: getMMSubmissionsInit,
+    GET_MM_SUBMISSIONS_DONE: getMMSubmissionsDone,
   },
 });
diff --git a/src/config/index.js b/src/config/index.js
deleted file mode 100644
index 2a5ab790..00000000
--- a/src/config/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-module.exports = {
-  PAGE_SIZE: 50,
-  REVIEW_OPPORTUNITY_PAGE_SIZE: 1000,
-};
diff --git a/src/index.js b/src/index.js
index 15b0e7d7..7dcdfde6 100644
--- a/src/index.js
+++ b/src/index.js
@@ -12,5 +12,5 @@ export { actions } from './actions';
 export { services } from './services';
 
 export {
-  challenge, logger, errors, tc, time, mock, url,
+  challenge, logger, errors, tc, time, mock, url, submission,
 } from './utils';
diff --git a/src/reducers/challenge.js b/src/reducers/challenge.js
index 889623e1..2d5b47c9 100644
--- a/src/reducers/challenge.js
+++ b/src/reducers/challenge.js
@@ -113,6 +113,44 @@ function onGetSubmissionsDone(state, action) {
   };
 }
 
+/**
+ * Handles CHALLENGE/GET_MM_SUBMISSION_INIT action.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
+function onGetMMSubmissionsInit(state, action) {
+  return {
+    ...state,
+    loadingMMSubmissionsForChallengeId: action.payload,
+    mmSubmissions: [],
+  };
+}
+
+/**
+ * Handles CHALLENGE/GET_MM_SUBMISSION_DONE action.
+ * @param {Object} state Previous state.
+ * @param {Object} action Action.
+ */
+function onGetMMSubmissionsDone(state, action) {
+  if (action.error) {
+    logger.error('Failed to get Marathon Match submissions for the challenge', action.payload);
+    return {
+      ...state,
+      loadingMMSubmissionsForChallengeId: '',
+      mmSubmissions: [],
+    };
+  }
+
+  const { challengeId, submissions } = action.payload;
+  if (challengeId.toString() !== state.loadingMMSubmissionsForChallengeId) return state;
+  return {
+    ...state,
+    loadingMMSubmissionsForChallengeId: '',
+    mmSubmissions: submissions,
+  };
+}
+
 /**
  * Handles challengeActions.fetchCheckpointsDone action.
  * @param {Object} state Previous state.
@@ -294,6 +332,8 @@ function create(initialState) {
     [a.getDetailsDone]: onGetDetailsDone,
     [a.getSubmissionsInit]: onGetSubmissionsInit,
     [a.getSubmissionsDone]: onGetSubmissionsDone,
+    [a.getMmSubmissionsInit]: onGetMMSubmissionsInit,
+    [a.getMmSubmissionsDone]: onGetMMSubmissionsDone,
     [smpActions.smp.deleteSubmissionDone]: (state, { payload }) => ({
       ...state,
       mySubmissions: {
@@ -324,6 +364,7 @@ function create(initialState) {
     loadingCheckpoints: false,
     loadingDetailsForChallengeId: '',
     loadingResultsForChallengeId: '',
+    loadingMMSubmissionsForChallengeId: '',
     mySubmissions: {},
     checkpoints: null,
     registering: false,
@@ -331,6 +372,7 @@ function create(initialState) {
     resultsLoadedForChallengeId: '',
     unregistering: false,
     updatingChallengeUuid: '',
+    mmSubmissions: [],
   }));
 }
 
diff --git a/src/services/api.js b/src/services/api.js
index 7c1e3355..97a045a1 100644
--- a/src/services/api.js
+++ b/src/services/api.js
@@ -264,6 +264,7 @@ export function getApi(version, token) {
 export const getApiV2 = token => getApi('V2', token);
 export const getApiV3 = token => getApi('V3', token);
 export const getApiV4 = token => getApi('V4', token);
+export const getApiV5 = token => getApi('V5', token);
 
 /**
  * Gets a valid TC M2M token, either requesting one from TC Auth0 API, or
diff --git a/src/services/index.js b/src/services/index.js
index b9707791..4d776832 100644
--- a/src/services/index.js
+++ b/src/services/index.js
@@ -14,6 +14,7 @@ import * as userSetting from './user-settings';
 import * as user from './user';
 import * as lookup from './lookup';
 import * as userTraits from './user-traits';
+import * as submissions from './submissions';
 
 export const services = {
   api,
@@ -29,6 +30,7 @@ export const services = {
   reviewOpportunities,
   lookup,
   userTraits,
+  submissions,
 };
 
 export default undefined;
diff --git a/src/services/members.js b/src/services/members.js
index 62afbf36..9afa0311 100644
--- a/src/services/members.js
+++ b/src/services/members.js
@@ -289,6 +289,18 @@ class MembersService {
     const res = await this.private.api.get(`/members/${handle}/verify?token=${emailVerifyToken}`);
     return getApiResponsePayload(res);
   }
+
+  /**
+   * Get members information
+   * @param {Array} userIds the member ids
+   */
+  async getMembersInformation(userIds) {
+    const query = `query=${encodeURI(_.map(userIds, id => `userId:${id}`).join(' OR '))}`;
+    const limit = `limit=${userIds.length}`;
+    const url = `/members/_search?fields=userId%2Chandle%2CphotoURL%2CfirstName%2ClastName&${query}&${limit}`;
+    const res = await this.private.api.get(url);
+    return getApiResponsePayload(res);
+  }
 }
 
 let lastInstance = null;
diff --git a/src/services/submissions.js b/src/services/submissions.js
new file mode 100644
index 00000000..6de0e6c2
--- /dev/null
+++ b/src/services/submissions.js
@@ -0,0 +1,57 @@
+/**
+ * @module "services.submission"
+ * @desc This module provides a service for convenient manipulation with
+ *  Topcoder submissions via TC API. Currently only used for MM challenges
+ */
+
+import qs from 'qs';
+import { getApi } from './api';
+
+/**
+ * Submission service.
+ */
+class SubmissionsService {
+  /**
+   * Creates a new SubmissionService instance.
+   * @param {String} tokenV3 Optional. Auth token for Topcoder API v3.
+   */
+  constructor(tokenV3) {
+    this.private = {
+      apiV5: getApi('V5', tokenV3),
+      tokenV3,
+    };
+  }
+
+  /**
+   * Get submissions of challenge
+   * @param {Object} filters
+   * @param {Object} params
+   * @return {Promise} Resolves to the api response.
+   */
+  async getSubmissions(filters, params) {
+    const query = {
+      ...filters,
+      ...params,
+    };
+    const url = `/submissions?${qs.stringify(query, { encode: false })}`;
+    return this.private.apiV5.get(url)
+      .then(res => (res.ok ? res.json() : new Error(res.statusText)))
+      .then(res => res);
+  }
+}
+
+let lastInstance = null;
+/**
+ * Returns a new or existing submissions service.
+ * @param {String} tokenV3 Optional. Auth token for Topcoder API v3.
+ * @return {SubmissionsService} Submissions service object
+ */
+export function getService(tokenV3) {
+  if (!lastInstance || lastInstance.private.tokenV3 !== tokenV3) {
+    lastInstance = new SubmissionsService(tokenV3);
+  }
+  return lastInstance;
+}
+
+/* Using default export would be confusing in this case. */
+export default undefined;
diff --git a/src/utils/index.js b/src/utils/index.js
index e7e4ff52..e3fc752f 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -10,6 +10,7 @@ import * as filter from './challenge/filter';
 import * as buckets from './challenge/buckets';
 import * as sort from './challenge/sort';
 import * as url from './url';
+import * as submission from './submission';
 
 const challenge = {
   filter,
@@ -25,4 +26,5 @@ export {
   mock,
   errors,
   url,
+  submission,
 };
diff --git a/src/utils/submission.js b/src/utils/submission.js
new file mode 100644
index 00000000..d3bec7e4
--- /dev/null
+++ b/src/utils/submission.js
@@ -0,0 +1,177 @@
+/**
+ * Various submissions functions.
+ */
+/* global CONFIG */
+/* eslint-disable no-param-reassign */
+import _ from 'lodash';
+
+const { AV_SCAN_SCORER_REVIEW_TYPE_ID } = CONFIG;
+
+function round(num, decimal) {
+  if (_.isNaN(num)) {
+    return 0;
+  }
+  const p1 = 10 ** (decimal + 1);
+  const p2 = 10 ** decimal;
+  return Math.round(num * p1 / 10) / p2;
+}
+
+function removeDecimal(num, decimal) {
+  return ((num % decimal) + decimal) % decimal;
+}
+
+function toFixed(num, decimal) {
+  const result = _.toFinite(round(num, decimal).toFixed(decimal));
+  const integerResult = _.toFinite(removeDecimal(result, decimal));
+  if (_.isInteger(integerResult)) {
+    return integerResult;
+  }
+  return result;
+}
+
+function getMMChallengeHandleStyle(handle, registrants) {
+  const style = _.get(_.find(registrants, m => m.handle === handle), 'colorStyle', null);
+  if (style) return JSON.parse(style.replace(/(\w+):\s*([^;]*)/g, '{"$1": "$2"}'));
+  return {};
+}
+
+/**
+ * Process each submission rank of MM challenge
+ * @param submissions the array of submissions
+ */
+function processRanks(submissions) {
+  let maxFinalScore = 0;
+  submissions.sort((a, b) => {
+    let pA = _.get(a, 'submissions[0]', { provisionalScore: 0 }).provisionalScore;
+    let pB = _.get(b, 'submissions[0]', { provisionalScore: 0 }).provisionalScore;
+    if (pA === '-') pA = 0;
+    if (pB === '-') pB = 0;
+    return pB - pA;
+  });
+  _.each(submissions, (submission, i) => {
+    submissions[i].provisionalRank = i + 1;
+  });
+
+  submissions.sort((a, b) => {
+    let pA = _.get(a, 'submissions[0]', { provisionalScore: 0 }).finalScore;
+    let pB = _.get(b, 'submissions[0]', { provisionalScore: 0 }).finalScore;
+    if (pA === '-') pA = 0;
+    if (pB === '-') pB = 0;
+    if (pA > 0) maxFinalScore = pA;
+    if (pB > 0) maxFinalScore = pB;
+    return pB - pA;
+  });
+  if (maxFinalScore > 0) {
+    _.each(submissions, (submission, i) => {
+      submissions[i].finalRank = i + 1;
+    });
+  }
+  return { submissions, maxFinalScore };
+}
+
+/**
+ * Get provisional score of submission
+ * @param submission
+ */
+export function getProvisionalScore(submission) {
+  const { submissions: subs } = submission;
+  if (!subs || subs.length === 0) {
+    return 0;
+  }
+  const { provisionalScore } = subs[0];
+  if (!provisionalScore || provisionalScore < 0) {
+    return 0;
+  }
+  return provisionalScore;
+}
+
+/**
+ * Get final score of submission
+ * @param submission
+ */
+export function getFinalScore(submission) {
+  const { submissions: subs } = submission;
+  if (!subs || subs.length === 0) {
+    return 0;
+  }
+  const { finalScore } = subs[0];
+  if (!finalScore || finalScore < 0) {
+    return 0;
+  }
+  return finalScore;
+}
+
+/**
+ * Process submissions of MM challenge
+ * @param submissions the array of submissions
+ * @param resources the challenge resources
+ * @param registrants the challenge registrants
+ */
+export function processMMSubmissions(submissions, resources, registrants) {
+  const data = {};
+  const result = [];
+
+  _.each(submissions, (submission) => {
+    const { memberId } = submission;
+    let memberHandle;
+    const resource = _.find(resources, r => _.get(r, 'userId').toString() === memberId.toString());
+    if (_.isEmpty(resource)) {
+      memberHandle = memberId;
+    } else {
+      memberHandle = _.has(resource, 'handle') ? _.get(resource, 'handle') : memberId.toString();
+    }
+    if (!data[memberHandle]) {
+      data[memberHandle] = [];
+    }
+    const validReviews = _.filter(submission.review,
+      r => !_.isEmpty(r) && (r.typeId !== AV_SCAN_SCORER_REVIEW_TYPE_ID));
+    validReviews.sort((a, b) => {
+      const dateA = new Date(a.created);
+      const dateB = new Date(b.created);
+      return dateB - dateA;
+    });
+    let provisionalScore;
+    if (validReviews.length > 0) {
+      provisionalScore = _.get(validReviews, '[0].score', 0);
+      if (_.isString(provisionalScore)) provisionalScore = _.toFinite(provisionalScore);
+      provisionalScore = toFixed(provisionalScore, 5);
+    } else {
+      provisionalScore = -1;
+    }
+
+    let finalScore = _.get(submission, 'reviewSummation[0].aggregateScore', 0);
+    if (_.isString(finalScore)) finalScore = _.toFinite(finalScore);
+    if (finalScore > 0) {
+      finalScore = toFixed(finalScore, 5);
+    } else {
+      finalScore = 0;
+    }
+    data[memberHandle].push({
+      submissionId: submission.id,
+      submissionTime: submission.created,
+      provisionalScore,
+      finalScore,
+    });
+  });
+
+  _.each(data, (value, key) => {
+    result.push({
+      submissions: [...value.sort((a, b) => new Date(b.submissionTime)
+        .getTime() - new Date(a.submissionTime).getTime())],
+      member: key,
+      colorStyle: getMMChallengeHandleStyle(key, registrants),
+    });
+  });
+
+  const { submissions: finalSubmissions, maxFinalScore } = processRanks(result);
+  finalSubmissions.sort((a, b) => {
+    if (maxFinalScore === 0) {
+      return a.provisionalRank - b.provisionalRank;
+    }
+    return a.finalRank - b.finalRank;
+  });
+
+  return finalSubmissions;
+}
+
+export default undefined;