diff --git a/gradle.properties b/gradle.properties
index cb06f8f26b77edd164e23c43d752185c93c3bfb1..59ab9e79e40dc7b657035a2ccb3f120e43d82450 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -10,7 +10,7 @@ micronautRxVersion = 2.1.1
 micronautPluginVersion = 3.1.1
 nodePluginVersion = 3.0.1
 nodeVersion = 16.3.0
-nshmpLibVersion = 0.9.1
+nshmpLibVersion = 0.9.3
 nshmpWsUtilsVersion = 0.1.7
 shadowVersion = 7.1.2
 spotbugsVersion = 4.7.0
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/DisaggCalc.java b/src/main/java/gov/usgs/earthquake/nshmp/DisaggCalc.java
index dd59717afd96208dc9e6e6dfb2734cc2617f9607..de59f18b22b4d55af6e5b48e9a2daea542165798 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/DisaggCalc.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/DisaggCalc.java
@@ -3,6 +3,9 @@ package gov.usgs.earthquake.nshmp;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static gov.usgs.earthquake.nshmp.Text.NEWLINE;
+import static gov.usgs.earthquake.nshmp.calc.DataType.DISAGG_DATA;
+import static gov.usgs.earthquake.nshmp.calc.DataType.GMM;
+import static gov.usgs.earthquake.nshmp.calc.DataType.SOURCE;
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
 
@@ -166,16 +169,11 @@ public class DisaggCalc {
        */
       Path out;
       if (siteColumns.size() == allColumns.size()) {
+
         checkArgument(
             modelImts.containsAll(config.hazard.imts),
             "Config specifies IMTs not supported by model");
-
-        // List<Imt> imts = config.imts;
-
-        // Path out = calc(model, config, sites, imtImlMaps, log);
-
         double returnPeriod = config.disagg.returnPeriod;
-
         out = calcRp(model, config, sites, returnPeriod, log);
 
       } else {
@@ -189,19 +187,9 @@ public class DisaggCalc {
             sites.size() == imls.size(),
             "Sites and spectra lists different sizes");
         log.info("Spectra: " + imls.size()); // 1:1 with sites
-
         out = calcIml(model, config, sites, imls, log);
-      }
-
-      // List<Map<Imt, Double>> imtImlMaps = readSpectra(siteFile, imts,
-      // colsToSkip);
-      // log.info("Spectra: " + imtImlMaps.size());
-
-      // checkArgument(sites.size() == imtImlMaps.size(), "Sites and spectra
-      // lists different sizes");
-      // Spectra should be checked against IMTs supported by model GMMs
 
-      // Path out = calc(model, config, sites, imls, log);
+      }
 
       log.info(PROGRAM + ": finished");
 
@@ -307,16 +295,13 @@ public class DisaggCalc {
       Site site = sites.get(i);
 
       Hazard hazard = HazardCalcs.hazard(model, config, site, exec);
+      handler.write(hazard);
 
       Map<Imt, Double> imls = imlsForReturnPeriod(hazard, returnPeriod);
-
       Disaggregation disagg = Disaggregation.atImls(hazard, imls, exec);
 
-      // needs to handle disagg same way as iml
-      // handler.write(hazard, Optional.of(disagg));
-      handler.write(hazard, Optional.empty());
-
       Response response = new Response.Builder()
+          .config(config)
           .site(site)
           .returnPeriod(returnPeriod)
           .imls(imls)
@@ -346,13 +331,13 @@ public class DisaggCalc {
   }
 
   /* Hazard curves are already in log-x space. */
-  static final Interpolator IML_INTERPOLATER = Interpolator.builder()
+  private static final Interpolator IML_INTERPOLATER = Interpolator.builder()
       .logy()
       .decreasingY()
       .build();
 
-  // this should be in a factory
-  private static Map<Imt, Double> imlsForReturnPeriod(
+  /** Compute the return period intercepts from a hazard result. */
+  public static Map<Imt, Double> imlsForReturnPeriod(
       Hazard hazard,
       double returnPeriod) {
 
@@ -360,7 +345,6 @@ public class DisaggCalc {
     Map<Imt, Double> imls = new EnumMap<>(Imt.class);
     for (Entry<Imt, XySequence> entry : hazard.curves().entrySet()) {
       double iml = IML_INTERPOLATER.findX(entry.getValue(), rate);
-      // remove exp below by transforming disagg-epsilon to log earlier
       imls.put(entry.getKey(), Math.exp(iml));
     }
     return imls;
@@ -408,6 +392,7 @@ public class DisaggCalc {
       Disaggregation disagg = Disaggregation.atImls(hazard, siteImls, exec);
 
       Response response = new Response.Builder()
+          .config(config)
           .site(site)
           .imls(siteImls)
           .disagg(disagg)
@@ -470,10 +455,11 @@ public class DisaggCalc {
 
     static final class Builder {
 
-      Disaggregation disagg;
       Site site;
+      Disaggregation disagg;
       Double returnPeriod; // optional
       Map<Imt, Double> imls;
+      CalcConfig config;
 
       Builder imls(Map<Imt, Double> imls) {
         this.imls = imls;
@@ -495,10 +481,20 @@ public class DisaggCalc {
         return this;
       }
 
+      Builder config(CalcConfig config) {
+        this.config = config;
+        return this;
+      }
+
       Response build() {
 
+        // default toJson(imt, false, false, false)
         List<ImtDisagg> disaggs = imls.keySet().stream()
-            .map(imt -> new ImtDisagg(imt, disagg.toJson(imt)))
+            .map(imt -> new ImtDisagg(imt, disagg.toJson(
+                imt,
+                config.output.dataTypes.contains(GMM),
+                config.output.dataTypes.contains(SOURCE),
+                config.output.dataTypes.contains(DISAGG_DATA))))
             .collect(toList());
 
         return new Response(
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java
index 27ab4200b2d48cab86285d437dffc3bb24170e31..a525b1b8c1124939e10f9383e5f43409f8d99297 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java
@@ -5,9 +5,12 @@ import static com.google.common.base.Preconditions.checkArgument;
 import java.util.Map;
 import java.util.Set;
 
+import gov.usgs.earthquake.nshmp.calc.DataType;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
+import gov.usgs.earthquake.nshmp.www.hazard.DisaggService.DisaggDataType;
+import gov.usgs.earthquake.nshmp.www.hazard.HazardService.HazardImt;
 
 import io.micronaut.core.annotation.Nullable;
 import io.micronaut.http.HttpRequest;
@@ -63,11 +66,9 @@ public class DisaggController {
    * @param returnPeriod The return period of the target ground motion, or
    *        intensity measure level (IML), in the range [1..20000] years.
    * @param imt Optional IMTs at which to compute hazard. If none are supplied,
-   *        then the supported set for the installed model is used. Note that a
-   *        model may not support all the values listed below (see
-   *        disagreggation metadata). Responses for numerous IMT's are quite
-   *        large, on the order of MB. Multiple IMTs may be comma delimited,
-   *        e.g. ?imt=PGA,SA0p2,SA1P0.
+   *        then the supported set for the installed model is used. Responses
+   *        for numerous IMT's are quite large, on the order of MB.
+   * @param out The data types to output
    */
   @Operation(
       summary = "Disaggregate hazard at a specified return period",
@@ -92,14 +93,17 @@ public class DisaggController {
       @Schema(
           minimum = "150",
           maximum = "3000") @PathVariable double returnPeriod,
-      @Schema() @QueryValue @Nullable Set<Imt> imt) {
+      @QueryValue @Nullable Set<HazardImt> imt,
+      @QueryValue @Nullable Set<DisaggDataType> out) {
     try {
       Set<Imt> imts = HazardService.readImts(http);
+      Set<DataType> dataTypes = HazardService.readDataTypes(http);
       DisaggService.RequestRp request = new DisaggService.RequestRp(
           http,
           longitude, latitude, vs30,
           returnPeriod,
-          imts);
+          imts,
+          dataTypes);
       return DisaggService.getDisaggRp(request);
     } catch (Exception e) {
       return ServletUtil.error(
@@ -113,6 +117,7 @@ public class DisaggController {
    * @param longitude Longitude in the range [-360..360]°.
    * @param latitude Latitude in decimal degrees [-90..90]°.
    * @param vs30 Site Vs30 value in the range [150..3000] m/s.
+   * @param out The data types to output
    */
   @Operation(
       summary = "Disaggregate hazard at specified IMLs",
@@ -133,23 +138,26 @@ public class DisaggController {
           maximum = "90") @PathVariable double latitude,
       @Schema(
           minimum = "150",
-          maximum = "3000") @PathVariable double vs30) {
+          maximum = "3000") @PathVariable double vs30,
+      @QueryValue @Nullable Set<DisaggDataType> out) {
 
     /*
      * Developer notes:
      *
      * It is awkward to support IMT=#; numerous unique keys that may or may not
      * be present yields a clunky swagger interface. The disagg-iml endpoint
-     * requires one or more IMT=# query arguments. Documented in example.
+     * requires one or more IMT=# query arguments. Document in example.
      */
 
     try {
       Map<Imt, Double> imtImlMap = http.getParameters().asMap(Imt.class, Double.class);
       checkArgument(!imtImlMap.isEmpty(), "No IMLs supplied");
+      Set<DataType> dataTypes = HazardService.readDataTypes(http);
       DisaggService.RequestIml request = new DisaggService.RequestIml(
           http,
           longitude, latitude, vs30,
-          imtImlMap);
+          imtImlMap,
+          dataTypes);
       return DisaggService.getDisaggIml(request);
     } catch (Exception e) {
       return ServletUtil.error(
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java
index 842194aaa68257ee447de8a9611b85dcbc2c475d..04c4b44afe320a329c758bf04ad8d9a9eba7ce32 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java
@@ -1,5 +1,8 @@
 package gov.usgs.earthquake.nshmp.www.hazard;
 
+import static gov.usgs.earthquake.nshmp.calc.DataType.DISAGG_DATA;
+import static gov.usgs.earthquake.nshmp.calc.DataType.GMM;
+import static gov.usgs.earthquake.nshmp.calc.DataType.SOURCE;
 import static java.util.stream.Collectors.toList;
 
 import java.util.List;
@@ -15,7 +18,9 @@ import org.slf4j.LoggerFactory;
 import com.google.common.base.Stopwatch;
 import com.google.common.collect.Range;
 
+import gov.usgs.earthquake.nshmp.DisaggCalc;
 import gov.usgs.earthquake.nshmp.calc.CalcConfig;
+import gov.usgs.earthquake.nshmp.calc.DataType;
 import gov.usgs.earthquake.nshmp.calc.Disaggregation;
 import gov.usgs.earthquake.nshmp.calc.Hazard;
 import gov.usgs.earthquake.nshmp.calc.HazardCalcs;
@@ -54,8 +59,15 @@ public final class DisaggService {
   private static Range<Double> rpRange = Range.closed(1.0, 20000.0);
   private static Range<Double> imlRange = Range.closed(0.0001, 8.0);
 
+  /* For Swagger selctions */
+  enum DisaggDataType {
+    GMM,
+    SOURCE,
+    DISAGG_DATA;
+  }
+
   /** HazardController.doGetMetadata() handler. */
-  public static HttpResponse<String> getMetadata(HttpRequest<?> request) {
+  static HttpResponse<String> getMetadata(HttpRequest<?> request) {
     var url = request.getUri().toString();
     var usage = new Metadata(ServletUtil.model());
     var response = ResponseBody.usage()
@@ -69,7 +81,7 @@ public final class DisaggService {
   }
 
   /** HazardController.doGetDisaggIml() handler. */
-  public static HttpResponse<String> getDisaggIml(RequestIml request)
+  static HttpResponse<String> getDisaggIml(RequestIml request)
       throws InterruptedException, ExecutionException {
     var stopwatch = Stopwatch.createStarted();
     var disagg = calcDisaggIml(request);
@@ -89,7 +101,7 @@ public final class DisaggService {
   }
 
   /** HazardController.doGetDisaggRp() handler. */
-  public static HttpResponse<String> getDisaggRp(RequestRp request)
+  static HttpResponse<String> getDisaggRp(RequestRp request)
       throws InterruptedException, ExecutionException {
     var stopwatch = Stopwatch.createStarted();
     var disagg = calcDisaggRp(request);
@@ -116,7 +128,7 @@ public final class DisaggService {
    *
    */
 
-  static Disaggregation calcDisaggIml(RequestIml request)
+  private static Disaggregation calcDisaggIml(RequestIml request)
       throws InterruptedException, ExecutionException {
 
     HazardModel model = ServletUtil.model();
@@ -124,6 +136,7 @@ public final class DisaggService {
     // modify config to include service endpoint arguments
     CalcConfig config = CalcConfig.copyOf(model.config())
         .imts(request.imls.keySet())
+        // .dataTypes(request.dataTypes)
         .build();
 
     // TODO this needs to pick up SiteData, centralize
@@ -152,7 +165,7 @@ public final class DisaggService {
     return disagg;
   }
 
-  static Disaggregation calcDisaggRp(RequestRp request)
+  private static Disaggregation calcDisaggRp(RequestRp request)
       throws InterruptedException, ExecutionException {
 
     HazardModel model = ServletUtil.model();
@@ -160,6 +173,7 @@ public final class DisaggService {
     // modify config to include service endpoint arguments
     CalcConfig config = CalcConfig.copyOf(model.config())
         .imts(request.imts)
+        // .dataTypes(request.dataTypes)
         .build();
 
     // TODO this needs to pick up SiteData, centralize
@@ -176,10 +190,13 @@ public final class DisaggService {
         ServletUtil.TASK_EXECUTOR);
 
     Hazard hazard = hazFuture.get();
+    Map<Imt, Double> imls = DisaggCalc.imlsForReturnPeriod(
+        hazard,
+        request.returnPeriod);
 
     CompletableFuture<Disaggregation> disaggfuture = CompletableFuture.supplyAsync(
-        () -> Disaggregation.atReturnPeriod(
-            hazard, request.returnPeriod,
+        () -> Disaggregation.atImls(
+            hazard, imls,
             ServletUtil.CALC_EXECUTOR),
         ServletUtil.TASK_EXECUTOR);
 
@@ -195,19 +212,22 @@ public final class DisaggService {
     final double latitude;
     final double vs30;
     final Map<Imt, Double> imls;
+    final Set<DataType> dataTypes;
 
     RequestIml(
         HttpRequest<?> http,
         double longitude,
         double latitude,
         double vs30,
-        Map<Imt, Double> imls) {
+        Map<Imt, Double> imls,
+        Set<DataType> dataTypes) {
 
       this.http = http;
       this.longitude = longitude;
       this.latitude = latitude;
       this.vs30 = vs30;
       this.imls = imls;
+      this.dataTypes = dataTypes;
     }
   }
 
@@ -219,6 +239,7 @@ public final class DisaggService {
     final double vs30;
     final double returnPeriod;
     final Set<Imt> imts;
+    final Set<DataType> dataTypes;
 
     RequestRp(
         HttpRequest<?> http,
@@ -226,7 +247,8 @@ public final class DisaggService {
         double latitude,
         double vs30,
         double returnPeriod,
-        Set<Imt> imts) {
+        Set<Imt> imts,
+        Set<DataType> dataTypes) {
 
       this.http = http;
       this.longitude = longitude;
@@ -236,6 +258,7 @@ public final class DisaggService {
       this.imts = imts.isEmpty()
           ? ServletUtil.model().config().hazard.imts
           : imts;
+      this.dataTypes = dataTypes;
     }
   }
 
@@ -267,6 +290,7 @@ public final class DisaggService {
       Optional<RequestRp> requestRp = Optional.empty();
       Optional<RequestIml> requestIml = Optional.empty();
       Disaggregation disagg;
+      CalcConfig config;
 
       Builder timer(Stopwatch timer) {
         this.timer = timer;
@@ -293,8 +317,16 @@ public final class DisaggService {
             ? requestRp.orElseThrow().imts
             : requestIml.orElseThrow().imls.keySet();
 
+        Set<DataType> dataTypes = requestRp.isPresent()
+            ? requestRp.orElseThrow().dataTypes
+            : requestIml.orElseThrow().dataTypes;
+
         List<ImtDisagg> disaggs = imts.stream()
-            .map(imt -> new ImtDisagg(imt, disagg.toJson(imt)))
+            .map(imt -> new ImtDisagg(imt, disagg.toJson(
+                imt,
+                dataTypes.contains(GMM),
+                dataTypes.contains(SOURCE),
+                dataTypes.contains(DISAGG_DATA))))
             .collect(toList());
 
         Object server = ServletUtil.serverData(ServletUtil.THREAD_COUNT, timer);
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java
index 1646f6ae279182588d70025a44a000858b094a4c..03bc22142d9da072cdd6991f2269d389f1981095 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java
@@ -5,6 +5,7 @@ import java.util.Set;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
+import gov.usgs.earthquake.nshmp.www.hazard.HazardService.HazardImt;
 
 import io.micronaut.core.annotation.Nullable;
 import io.micronaut.http.HttpRequest;
@@ -60,11 +61,8 @@ public class HazardController {
    * @param truncate Truncate curves at return periods below ~10,000 years
    * @param maxdir Apply max-direction scaling
    * @param imt Optional IMTs at which to compute hazard. If none are supplied,
-   *        then the supported set for the installed model is used. Note that a
-   *        model may not support all the values listed below (see
-   *        disagreggation metadata). Responses for numerous IMT's are quite
-   *        large, on the order of MB. Multiple IMTs may be comma delimited,
-   *        e.g. ?imt=PGA,SA0p2,SA1P0.
+   *        then the supported set for the installed model is used. Responses
+   *        for numerous IMT's are quite large, on the order of MB.
    *
    */
   @Operation(
@@ -91,7 +89,7 @@ public class HazardController {
           defaultValue = "false") @Nullable Boolean truncate,
       @QueryValue(
           defaultValue = "false") @Nullable Boolean maxdir,
-      @QueryValue @Nullable Set<Imt> imt) {
+      @QueryValue @Nullable Set<HazardImt> imt) {
     try {
       Set<Imt> imts = HazardService.readImts(http);
       HazardService.Request request = new HazardService.Request(
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
index f668b016e76468c696e0759fa3cee55077c2451e..c5c278ee2d53ed744cb999c07d06413dd0c52897 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
@@ -23,6 +23,7 @@ import org.slf4j.LoggerFactory;
 import com.google.common.base.Stopwatch;
 
 import gov.usgs.earthquake.nshmp.calc.CalcConfig;
+import gov.usgs.earthquake.nshmp.calc.DataType;
 import gov.usgs.earthquake.nshmp.calc.Hazard;
 import gov.usgs.earthquake.nshmp.calc.HazardCalcs;
 import gov.usgs.earthquake.nshmp.calc.Site;
@@ -57,6 +58,33 @@ public final class HazardService {
 
   private static final String TOTAL_KEY = "Total";
 
+  /* For Swagger selections; mprs + pgv */
+  enum HazardImt {
+    PGA,
+    PGV,
+    SA0P01,
+    SA0P02,
+    SA0P03,
+    SA0P05,
+    SA0P075,
+    SA0P1,
+    SA0P15,
+    SA0P2,
+    SA0P25,
+    SA0P3,
+    SA0P4,
+    SA0P5,
+    SA0P75,
+    SA1P0,
+    SA1P5,
+    SA2P0,
+    SA3P0,
+    SA4P0,
+    SA5P0,
+    SA7P5,
+    SA10P0;
+  }
+
   /** HazardController.doGetUsage() handler. */
   public static HttpResponse<String> getMetadata(HttpRequest<?> request) {
     var url = request.getUri().toString();
@@ -155,7 +183,7 @@ public final class HazardService {
     }
   }
 
-  public static final class Request {
+  static final class Request {
 
     final transient HttpRequest<?> http;
     final double longitude;
@@ -347,12 +375,19 @@ public final class HazardService {
 
   /* Read the 'imt' query values; can be comma-delimited. */
   static Set<Imt> readImts(HttpRequest<?> http) {
-    return http.getParameters()
-        .getAll("imt")// TODO where are key strings?
-        .stream()
+    return http.getParameters().getAll("imt").stream()
         .map(s -> s.split(","))
         .flatMap(Arrays::stream)
         .map(Imt::valueOf)
         .collect(toCollection(() -> EnumSet.noneOf(Imt.class)));
   }
+
+  /* Read the 'imt' query values; can be comma-delimited. */
+  static Set<DataType> readDataTypes(HttpRequest<?> http) {
+    return http.getParameters().getAll("out").stream()
+        .map(s -> s.split(","))
+        .flatMap(Arrays::stream)
+        .map(DataType::valueOf)
+        .collect(toCollection(() -> EnumSet.noneOf(DataType.class)));
+  }
 }