diff --git a/__tests__/__snapshots__/index.js.snap b/__tests__/__snapshots__/index.js.snap
index 1da4407c..31e05077 100644
--- a/__tests__/__snapshots__/index.js.snap
+++ b/__tests__/__snapshots__/index.js.snap
@@ -67,6 +67,17 @@ Object {
       "getSkillTagsDone": [Function],
       "getSkillTagsInit": [Function],
     },
+    "memberSearch": Object {
+      "checkIfSearchTermIsATag": [Function],
+      "clearMemberSearch": [Function],
+      "loadMoreUsernames": [Function],
+      "memberSearchSuccess": [Function],
+      "resetSearchTerm": [Function],
+      "setSearchTag": [Function],
+      "setSearchTerm": [Function],
+      "topMemberSearchSuccess": [Function],
+      "usernameSearchSuccess": [Function],
+    },
     "memberTasks": Object {
       "dropAll": [Function],
       "getDone": [Function],
@@ -240,6 +251,7 @@ Object {
     "groups": [Function],
     "looker": [Function],
     "lookup": [Function],
+    "memberSearch": [Function],
     "memberTasks": [Function],
     "members": [Function],
     "mySubmissionsManagement": [Function],
@@ -292,6 +304,10 @@ Object {
       "default": undefined,
       "getService": [Function],
     },
+    "memberSearch": Object {
+      "default": undefined,
+      "getService": [Function],
+    },
     "members": Object {
       "default": undefined,
       "getService": [Function],
diff --git a/package.json b/package.json
index b8e64b88..c04041ab 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
     "lint:js": "./node_modules/.bin/eslint --ext .js,.jsx .",
     "test": "npm run lint && npm run jest"
   },
-  "version": "0.11.1",
+  "version": "0.12.0",
   "dependencies": {
     "auth0-js": "^6.8.4",
     "config": "^3.2.0",
diff --git a/src/actions/index.js b/src/actions/index.js
index 8b1a241b..09deaca7 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -13,6 +13,7 @@ import reviewOpportunityActions from './reviewOpportunity';
 import lookupActions from './lookup';
 import settingsActions from './settings';
 import lookerActions from './looker';
+import memberSearchActions from './member-search';
 
 export const actions = {
   auth: authActions.auth,
@@ -30,6 +31,7 @@ export const actions = {
   lookup: lookupActions.lookup,
   settings: settingsActions.settings,
   looker: lookerActions.looker,
+  memberSearch: memberSearchActions.memberSearch,
 };
 
 export default undefined;
diff --git a/src/actions/member-search.js b/src/actions/member-search.js
new file mode 100644
index 00000000..7b5694bd
--- /dev/null
+++ b/src/actions/member-search.js
@@ -0,0 +1,80 @@
+/**
+ * @module "actions.member-search"
+ * @desc Actions for management of members search.
+ */
+import _ from 'lodash';
+import { createActions } from 'redux-actions';
+import { getService } from '../services/member-search';
+
+/**
+ * @desc Creates an action that fetchs the members data for a search term, and
+ * adds result to the store cumulatively.
+ * @param {String} searchTerm the search term
+ * @param {Number} offset the number of records to skip
+ * @param {Number} limit the maximum number of the return results
+ * @return {Action}
+ */
+function loadMemberSearch(searchTerm, offset = 0, limit = 10) {
+  return getService().getUsernameMatches(searchTerm, offset, limit);
+}
+
+/**
+ * @static
+ * @desc Creates an action that fetchs the members data for a search tag, and
+ * adds result to the store.
+ * @param {Object} tag the tag
+ * @return {Action}
+ */
+function loadMemberSearchForTag(tag) {
+  return getService().getTopMembers(tag);
+}
+
+/**
+ * @static
+ * @desc Creates an action that check if the term is a tag name. If it is unable to check,
+ * or invalid data returned then resets the members data and search terms data in the store
+ * to intial values.
+ * @param {String} searchTerm the search term
+ * @return {Action}
+ */
+function checkIfSearchTermIsATag(searchTerm) {
+  return getService().checkIfSearchTermIsATag(searchTerm);
+}
+
+/**
+ * @static
+ * @desc Creates an action that saves the current search term.
+ * @param {String} searchTerm the search term
+ * @return {Action}
+ */
+function setSearchTerm(searchTerm) {
+  return {
+    previousSearchTerm: searchTerm,
+  };
+}
+
+/**
+ * @static
+ * @desc Creates an action that saves the current search tag.
+ * @param {Object} searchTag the search tag
+ * @return {Action}
+ */
+function setSearchTag(searchTag) {
+  return {
+    searchTermTag: searchTag,
+  };
+}
+
+export default createActions({
+  MEMBER_SEARCH: {
+    USERNAME_SEARCH_SUCCESS: loadMemberSearch,
+    CHECK_IF_SEARCH_TERM_IS_A_TAG: checkIfSearchTermIsATag,
+    TOP_MEMBER_SEARCH_SUCCESS: loadMemberSearchForTag,
+    CLEAR_MEMBER_SEARCH: _.noop,
+    LOAD_MORE_USERNAMES: _.noop,
+    MEMBER_SEARCH_SUCCESS: _.noop,
+    SET_SEARCH_TERM: setSearchTerm,
+    SET_SEARCH_TAG: setSearchTag,
+    RESET_SEARCH_TERM: _.noop,
+  },
+});
diff --git a/src/reducers/index.js b/src/reducers/index.js
index 15fd144c..7f7e3604 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -22,7 +22,7 @@ import settings, { factory as settingsFactory }
   from './settings';
 import looker, { factory as lookerFactory }
   from './looker';
-
+import memberSearch, { factory as memberSearchFactory } from './member-search';
 
 export function factory(options) {
   return redux.resolveReducers({
@@ -41,6 +41,7 @@ export function factory(options) {
     mySubmissionsManagement: mySubmissionsManagementFactory(options),
     settings: settingsFactory(options),
     looker: lookerFactory(options),
+    memberSearch: memberSearchFactory(options),
   });
 }
 
@@ -60,4 +61,5 @@ export default ({
   mySubmissionsManagement,
   settings,
   looker,
+  memberSearch,
 });
diff --git a/src/reducers/member-search.js b/src/reducers/member-search.js
new file mode 100644
index 00000000..bc57ae3b
--- /dev/null
+++ b/src/reducers/member-search.js
@@ -0,0 +1,245 @@
+/**
+ * @module "reducers.member-search"
+ * @desc Reducer for {@link module:actions.member-search} actions.
+ *
+ * State segment managed by this reducer has the following structure:
+ * @param {Boolean} pageLoaded  `true` if loading members data for a search term is done
+ *                              `false`if starting loading members data for a search term,
+ * or loading failed
+ * @param {Boolean} loadingMore `true` if request for loading more data is in progress;
+ * otherwise `false`
+ * @param {Boolean} error       `true` if failed to load member data; otherwise `false`
+ * @param {Number}  totalCount   the number of matched members for a search term
+ * @param {Boolean} moreMatchesAvailable `true` if there are more matched members, for
+ * a search term, to load; otherwise `false`
+ * @param {Array<{}>} usernameMatches contains loaded members data for a search term
+ * @param {Array<{}>} topMembers contains loaded members data for a search tag
+ * @param {String} previousSearchTerm the current search term
+ * @param {Object} searchTermTag the current search tag data if the search term is a tag name;
+ * otherwise `null`
+ */
+import _ from 'lodash';
+import { redux } from 'topcoder-react-utils';
+import actions from '../actions/member-search';
+import { fireErrorMessage } from '../utils/errors';
+
+/**
+ * @private
+ * Returns the new state with the intial members data.
+ */
+function memberSearchFailure(state) {
+  return Object.assign({}, state, {
+    loadingMore: false,
+    error: true,
+    totalCount: 0,
+    usernameMatches: [],
+    topMembers: [],
+  });
+}
+
+/**
+ * @private
+ * Returns the new state with the intial search terms data.
+ */
+function resetSearchTerm(state) {
+  return Object.assign({}, state, {
+    pageLoaded: false,
+    previousSearchTerm: null,
+    searchTermTag: null,
+  });
+}
+
+/**
+ * @private
+ * Returns the new state with the intial members and search terms data.
+ */
+function memberSearchFailureAndResetSearchTerm(state) {
+  let newState = state;
+  newState = memberSearchFailure(newState);
+  newState = resetSearchTerm(newState);
+  return newState;
+}
+
+/**
+ * Handles the actual results of loading members data for a search term cumulatively,
+ * and clear members data on request failure.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
+function onUsernameSearchSuccess(state, action) {
+  const { payload } = action;
+  if (action.error) {
+    fireErrorMessage('Could not fetch username matches', '');
+    return memberSearchFailure(state);
+  }
+
+  return Object.assign({}, state, {
+    loadingMore: false,
+    totalCount: payload.totalCount,
+    moreMatchesAvailable: state.usernameMatches.length + payload.usernameMatches.length
+      < payload.totalCount,
+    usernameMatches: state.usernameMatches.concat(payload.usernameMatches),
+  });
+}
+
+/**
+ * Clear members data and search terms data on request failure of checking if the search term is
+ * a tag name.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state if error; otherwise the same state.
+ */
+function onCheckIfSearchTermIsATag(state, action) {
+  if (action.error) {
+    fireErrorMessage('Could not determine if search term is a tag', '');
+    return memberSearchFailureAndResetSearchTerm(state);
+  }
+
+  return state;
+}
+
+/**
+ * Handles the actual results of loading members data for a search tag, and
+ * clear members data and search terms data on request failure.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
+function onTopMemberSearchSuccess(state, action) {
+  const { payload } = action;
+  if (action.error) {
+    fireErrorMessage('Could not fetch top members', '');
+    return memberSearchFailureAndResetSearchTerm(state);
+  }
+
+  return Object.assign({}, state, {
+    topMembers: payload.topMembers,
+  });
+}
+
+/**
+ * Clear members data to the intial state.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
+function onClearMemberSearch(state) {
+  return Object.assign({}, state, {
+    pageLoaded: false,
+    loadingMore: false,
+    error: false,
+    totalCount: 0,
+    usernameMatches: [],
+    topMembers: [],
+  });
+}
+
+/**
+ * Marks the request of loading more members data for a search term as in progress
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
+function onLoadMoreUsernames(state) {
+  return Object.assign({}, state, {
+    loadingMore: true,
+  });
+}
+
+/**
+ * Marks the loaded members data for a search term or search tag (if any) as ready.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
+function onMemberSearchSuccess(state) {
+  return Object.assign({}, state, {
+    pageLoaded: true,
+  });
+}
+
+/**
+ * Handles setting the current search term.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
+function onSetSearchTerm(state, action) {
+  const { payload } = action;
+  return Object.assign({}, state, {
+    error: false,
+    previousSearchTerm: payload.previousSearchTerm,
+  });
+}
+
+/**
+ * Handles setting the current search tag.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
+function onSetSearchTag(state, action) {
+  const { payload } = action;
+  return Object.assign({}, state, {
+    searchTermTag: payload.searchTermTag,
+  });
+}
+
+/**
+ * Handles clearing the current search term and search tag.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
+function onResetSearchTerm(state) {
+  return resetSearchTerm(state);
+}
+
+/**
+ * Creates a new member search reducer with the specified initial state.
+ * @param {Object} initialState Optional. Initial state.
+ * @return {Function} member search reducer.
+ */
+function create(initialState = {}) {
+  const a = actions.memberSearch;
+  return redux.handleActions({
+    [a.usernameSearchSuccess]: onUsernameSearchSuccess,
+    [a.checkIfSearchTermIsATag]: onCheckIfSearchTermIsATag,
+    [a.topMemberSearchSuccess]: onTopMemberSearchSuccess,
+    [a.clearMemberSearch]: onClearMemberSearch,
+    [a.loadMoreUsernames]: onLoadMoreUsernames,
+    [a.memberSearchSuccess]: onMemberSearchSuccess,
+    [a.setSearchTerm]: onSetSearchTerm,
+    [a.setSearchTag]: onSetSearchTag,
+    [a.resetSearchTerm]: onResetSearchTerm,
+  }, _.defaults(initialState, {
+    pageLoaded: false,
+    loadingMore: false,
+    error: false,
+    totalCount: 0,
+    moreMatchesAvailable: false,
+    usernameMatches: [],
+    topMembers: [],
+    previousSearchTerm: null,
+    searchTermTag: null,
+  }));
+}
+
+/**
+ * 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:
+ * @return {Promise}
+ * @resolves {Function(state, action): state} New reducer.
+ */
+export function factory() {
+  return Promise.resolve(create());
+}
+
+/**
+ * @static
+ * @member default
+ * @desc Reducer with default initial state.
+ */
+export default create();
diff --git a/src/services/index.js b/src/services/index.js
index 4d776832..d6b5993b 100644
--- a/src/services/index.js
+++ b/src/services/index.js
@@ -15,6 +15,7 @@ import * as user from './user';
 import * as lookup from './lookup';
 import * as userTraits from './user-traits';
 import * as submissions from './submissions';
+import * as memberSearch from './member-search';
 
 export const services = {
   api,
@@ -31,6 +32,7 @@ export const services = {
   lookup,
   userTraits,
   submissions,
+  memberSearch,
 };
 
 export default undefined;
diff --git a/src/services/member-search.js b/src/services/member-search.js
new file mode 100644
index 00000000..94f0ca25
--- /dev/null
+++ b/src/services/member-search.js
@@ -0,0 +1,121 @@
+/**
+ * @module "services.member-search"
+ * @desc This module provides a service for searching members.
+ */
+import _ from 'lodash';
+import qs from 'qs';
+import { getApi } from './api';
+import { checkResponseSucess, mapTagToLeaderboardType } from '../utils/member-search';
+
+/**
+ * Member search service class.
+ */
+class MemberSearchService {
+  /**
+   * @param {String} tokenV3 Optional. Auth token for Topcoder API v3.
+   */
+  constructor(tokenV3) {
+    this.private = {
+      api: getApi('V3', tokenV3),
+      tokenV3,
+    };
+  }
+
+  /**
+   * Get matched members for a search term.
+   * @param {String} searchTerm the search term
+   * @param {Number} offset the number of members to skip from starting
+   * @param {Number} limit the maximum number of return members
+   * @return {Promise} Resolves to an object containing the array of matched members
+   * and the total count
+   */
+  getUsernameMatches(searchTerm, offset, limit) {
+    const params = {
+      query: 'MEMBER_SEARCH',
+      handle: encodeURIComponent(searchTerm),
+      offset,
+      limit,
+    };
+
+    return this.private.api.get(`/members/_search/?${qs.stringify(params)}`)
+      .then(checkResponseSucess)
+      .then((data) => {
+        const usernameMatches = _.get(data, 'result.content');
+        const totalCount = _.get(data, 'result.metadata.totalCount');
+
+        if (!_.isArray(usernameMatches)) {
+          throw new Error('Expected array for username response results');
+        } else if (!_.isNumber(totalCount)) {
+          throw new Error('Expected number for metadata total count');
+        }
+
+        return {
+          usernameMatches,
+          totalCount,
+        };
+      })
+      .catch((err) => {
+        throw new Error(`Could not fetch username matches. Reason: ${err}`);
+      });
+  }
+
+  /**
+  * Check if the search term is a tag.
+  * @param {String} searchTerm the search term
+  * @return {Promise} Resolves to a tag object
+  */
+  checkIfSearchTermIsATag(searchTerm) {
+    return this.private.api.get(`/tags/?filter=name%3D${encodeURIComponent(searchTerm)}`)
+      .then(checkResponseSucess)
+      .then((data) => {
+        const tagInfo = _.get(data, 'result.content');
+
+        if (!_.isArray(tagInfo)) {
+          throw new Error('Tag response must be an array');
+        }
+
+        return tagInfo[0];
+      })
+      .catch((err) => {
+        throw new Error(`Could not determine if search term is a tag. Reason: ${err}`);
+      });
+  }
+
+  /**
+  * Get matched members for a search tag.
+  * @param {Object} tag the tag
+  * @return {Promise} Resolves to an object containing the array of matched members
+  */
+  getTopMembers(tag) {
+    const leaderboardType = mapTagToLeaderboardType(tag.domain);
+
+    return this.private.api.get(`/leaderboards/?filter=id%3D${tag.id}%26type%3D${leaderboardType}`)
+      .then(checkResponseSucess)
+      .then((data) => {
+        const topMembers = _.get(data, 'result.content', []);
+
+        return {
+          topMembers,
+        };
+      })
+      .catch((err) => {
+        throw new Error(`Could not fetch top members. Reason: ${err}`);
+      });
+  }
+}
+
+let lastInstance = null;
+
+/**
+ * Returns a new or existing member-search service.
+ * @param {String} tokenV3 Optional. Auth token for Topcoder API v3.
+ * @return {MemberSearchService} Member search service object
+ */
+export function getService(tokenV3) {
+  if (!lastInstance || tokenV3 !== lastInstance.private.tokenV3) {
+    lastInstance = new MemberSearchService(tokenV3);
+  }
+  return lastInstance;
+}
+
+export default undefined;
diff --git a/src/utils/member-search.js b/src/utils/member-search.js
new file mode 100644
index 00000000..1fba5b0b
--- /dev/null
+++ b/src/utils/member-search.js
@@ -0,0 +1,18 @@
+export function mapTagToLeaderboardType(tagDomain) {
+  const tagToLeaderboardTypeMap = {
+    SKILLS: 'MEMBER_SKILL',
+  };
+
+  return tagDomain ? tagToLeaderboardTypeMap[tagDomain.toUpperCase()] : null;
+}
+
+export async function checkResponseSucess(res) {
+  if (!res.ok) {
+    throw new Error(res.statusText);
+  }
+  const x = await res.json();
+  if (!x.result.success) {
+    throw new Error(x.result.content);
+  }
+  return x;
+}