You can achieve this by using dynamic parameters in your $stateProvider configuration defined on your sub-module. So you have some anchored routes on the main module, and if there is a match, ui-router will simply fetch the associated template. If there is no matched absolute route/url, then ui-router falls back on your parameterised route, like so:
// anchored
$stateProvider
  .state('mainAppState', {
    url: '/anchored',
    controller: 'myCtrl'
 })
// sub module
 .state('subAppState', {
    url: /:parameter
    resolve: { // use the resolve and $stateParams to get some parameter with which you can make a request to your API }
    templateProvider: { // inject a service and use it to call your backend }
You can use a the parameter, which gets passed to $stateParams, which gets passed to your resolve block functions, which can retrieve some data or process the data from $stateParams, that can then be used by templateProvider to provide a meaningful fallback URL. That's because templateProvider receives all resolves. It will always wait for all promises in the resolve block to resolve one way or another before executing.
That way you can catch any unmatched URLs from the main app in your sub app. Hope this helps.
See my issue here: ui-router: async data in templateUrl function
Oh and one more thing - if you are going to mangle your JS in your build process, make sure you define your templateProvider function with .$inject = [ ... ]. If not, you'll get an unknown provider error. ngAnnotate will NOT catch the dependencies in your templateProvider function. Alternatively you can just use /* @ngInject */, which worked for me.