diff --git a/gradle.properties b/gradle.properties
index 2d2aa1c4280abb7ce209c866f5ccdd0cfffc5f36..6d842ead731f80e01295e1d01548fce08b5f0507 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -8,7 +8,7 @@ micronautPluginVersion = 3.1.1
 nodePluginVersion = 3.0.1
 nodeVersion = 16.3.0
 nshmFaultSectionsTag = v0.1
-nshmpLibVersion = 0.8.2
+nshmpLibVersion = 0.9.11
 nshmpWsUtilsVersion = 0.1.7
 shadowVersion = 7.1.2
 spotbugsVersion = 4.7.0
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/DistanceService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/DistanceService.java
deleted file mode 100644
index 864711fda20e57e4e396d985f2d398c06bccb1c6..0000000000000000000000000000000000000000
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/DistanceService.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package gov.usgs.earthquake.nshmp.www.gmm;
-
-import java.util.Optional;
-import java.util.Set;
-
-import gov.usgs.earthquake.nshmp.gmm.Gmm;
-import gov.usgs.earthquake.nshmp.gmm.GmmInput;
-import gov.usgs.earthquake.nshmp.gmm.Imt;
-import gov.usgs.earthquake.nshmp.www.ResponseBody;
-import gov.usgs.earthquake.nshmp.www.gmm.Service.Id;
-import gov.usgs.earthquake.nshmp.www.gmm.Service.Response;
-
-import io.micronaut.http.HttpRequest;
-import io.micronaut.http.HttpResponse;
-import jakarta.inject.Singleton;
-
-@Singleton
-class DistanceService {
-
-  private static final double R_MIN = -100.0;
-  private static final double R_MAX = 100.0;
-  private final static int R_POINTS = 100;
-
-  static HttpResponse<String> processRequestDistance(
-      DistanceService.Request request) {
-
-    var rArray = distanceArray(request);
-    var gmvr = GroundMotions.versusDistance(
-        request.gmms,
-        request.input,
-        request.imt,
-        rArray);
-    var response = Response.create(request.serviceId, request, gmvr);
-    var body = ResponseBody.success()
-        .name(request.serviceId.name)
-        .url(request.http.getUri().getPath())
-        .request(request)
-        .response(response)
-        .build();
-    var json = Service.GSON.toJson(body);
-    return HttpResponse.ok(json);
-  }
-
-  private static double[] distanceArray(
-      DistanceService.Request request) {
-    var isLog = request.serviceId.equals(Id.DISTANCE) ? true : false;
-    var rStep = isLog
-        ? (Math.log10(request.rMax / request.rMin)) / (R_POINTS - 1)
-        : 1.0;
-    return isLog
-        ? GmmServices.sequenceLog(request.rMin, request.rMax, rStep)
-        : GmmServices.sequenceLinear(request.rMin, request.rMax, rStep);
-
-  }
-
-  static class Request extends Service.Request {
-
-    Imt imt;
-    double rMin;
-    double rMax;
-
-    Request(
-        HttpRequest<?> http, Service.Id serviceId,
-        Set<Gmm> gmms, GmmInput in,
-        Optional<Imt> imt,
-        Optional<Double> rMin,
-        Optional<Double> rMax) {
-
-      super(http, serviceId, gmms, in);
-      this.imt = imt.orElse(Imt.PGA);
-      this.rMin = rMin.orElse(R_MIN);
-      this.rMax = rMax.orElse(R_MAX);
-    }
-  }
-}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GmmController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GmmController.java
index 56bcffe80c9b9eca9bdd6b7c3ad496f20a33e742..525e960172d2f7311e049a7c2b54ba87edace9ae 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GmmController.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GmmController.java
@@ -8,7 +8,7 @@ import gov.usgs.earthquake.nshmp.gmm.GmmInput;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
 import gov.usgs.earthquake.nshmp.www.Utils;
-import gov.usgs.earthquake.nshmp.www.gmm.Service.Id;
+import gov.usgs.earthquake.nshmp.www.gmm.GmmService.Id;
 
 import io.micronaut.core.annotation.Nullable;
 import io.micronaut.http.HttpRequest;
@@ -26,7 +26,7 @@ import jakarta.inject.Inject;
 
 @Tag(name = "Ground Motion Models")
 @Controller("/gmm")
-public class GmmController {
+class GmmController {
 
   /*
    * Developer Notes
@@ -106,21 +106,20 @@ public class GmmController {
       responseCode = "200",
       content = @Content(schema = @Schema(type = "string")))
   @Get(uri = "/spectra", produces = MediaType.APPLICATION_JSON)
-  public HttpResponse<String> doGetSpectra(
-      HttpRequest<?> http) {
+  public HttpResponse<String> doGetSpectra(HttpRequest<?> http) {
     Id serviceId = Id.SPECTRA;
     try {
-      Optional<Set<Gmm>> gmms = GmmServices.readGmms(http);
+      Set<Gmm> gmms = GmmServices.readGmms(http);
       if (gmms.isEmpty()) {
-        return Service.metadata(http, serviceId);
+        return GmmServices.metadata(http, serviceId);
       }
       GmmInput in = GmmServices.readGmmInput(http);
-      Service.Request gmmRequest = new Service.Request(
+      GmmService.Request gmmRequest = new GmmService.Request(
           http,
           serviceId,
-          gmms.orElseThrow(),
+          gmms,
           in);
-      return SpectraService.processRequestSpectra(gmmRequest);
+      return GmmService.Spectra.processRequestSpectra(gmmRequest);
     } catch (Exception e) {
       return Utils.handleError(e, serviceId.name, http.getUri().getPath());
     }
@@ -227,17 +226,17 @@ public class GmmController {
           maximum = "10") @QueryValue @Nullable Double z2p5) {
     Id serviceId = Id.SPECTRA;
     try {
-      Optional<Set<Gmm>> gmms = GmmServices.readGmms(http);
+      Set<Gmm> gmms = GmmServices.readGmms(http);
       if (gmms.isEmpty()) {
-        return Service.metadata(http, serviceId);
+        return GmmServices.metadata(http, serviceId);
       }
       GmmInput in = GmmServices.readGmmInput(http);
-      Service.Request gmmRequest = new Service.Request(
+      GmmService.Request gmmRequest = new GmmService.Request(
           http,
           serviceId,
-          gmms.orElseThrow(),
+          gmms,
           in);
-      return SpectraService.processRequestSpectra(gmmRequest);
+      return GmmService.Spectra.processRequestSpectra(gmmRequest);
     } catch (Exception e) {
       return Utils.handleError(e, serviceId.name, http.getUri().getPath());
     }
@@ -357,16 +356,16 @@ public class GmmController {
           maximum = "10") @QueryValue @Nullable Double z2p5) {
     Id serviceId = Id.DISTANCE;
     try {
-      Optional<Set<Gmm>> gmms = GmmServices.readGmms(http);
+      Set<Gmm> gmms = GmmServices.readGmms(http);
       if (gmms.isEmpty()) {
-        return Service.metadata(http, serviceId);
+        return GmmServices.metadata(http, serviceId);
       }
       GmmInput in = GmmServices.readGmmInput(http);
-      DistanceService.Request request = new DistanceService.Request(
+      GmmService.Distance.Request request = new GmmService.Distance.Request(
           http, serviceId,
-          gmms.orElseThrow(), in,
+          gmms, in,
           imt, rMin, rMax);
-      return DistanceService.processRequestDistance(request);
+      return GmmService.Distance.processRequestDistance(request);
     } catch (Exception e) {
       return Utils.handleError(e, serviceId.name, http.getUri().getPath());
     }
@@ -468,16 +467,16 @@ public class GmmController {
 
     Id serviceId = Id.HW_FW;
     try {
-      Optional<Set<Gmm>> gmms = GmmServices.readGmms(http);
+      Set<Gmm> gmms = GmmServices.readGmms(http);
       if (gmms.isEmpty()) {
-        return Service.metadata(http, serviceId);
+        return GmmServices.metadata(http, serviceId);
       }
       GmmInput in = GmmServices.readGmmInput(http);
-      DistanceService.Request request = new DistanceService.Request(
+      GmmService.Distance.Request request = new GmmService.Distance.Request(
           http, serviceId,
-          gmms.orElseThrow(), in,
+          gmms, in,
           imt, rMin, rMax);
-      return DistanceService.processRequestDistance(request);
+      return GmmService.Distance.processRequestDistance(request);
     } catch (Exception e) {
       return Utils.handleError(e, serviceId.name, http.getUri().getPath());
     }
@@ -588,16 +587,16 @@ public class GmmController {
           maximum = "10") @QueryValue @Nullable Double z2p5) {
     Id serviceId = Id.MAGNITUDE;
     try {
-      Optional<Set<Gmm>> gmms = GmmServices.readGmms(http);
+      Set<Gmm> gmms = GmmServices.readGmms(http);
       if (gmms.isEmpty()) {
-        return Service.metadata(http, serviceId);
+        return GmmServices.metadata(http, serviceId);
       }
       GmmInput in = GmmServices.readGmmInput(http);
-      MagnitudeService.Request gmmRequest = new MagnitudeService.Request(
+      GmmService.Magnitude.Request gmmRequest = new GmmService.Magnitude.Request(
           http, serviceId,
-          gmms.orElseThrow(), in,
+          gmms, in,
           imt, mMin, mMax, step, distance);
-      return MagnitudeService.processRequestMagnitude(gmmRequest);
+      return GmmService.Magnitude.processRequestMagnitude(gmmRequest);
     } catch (Exception e) {
       return Utils.handleError(e, serviceId.name, http.getUri().getPath());
     }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/Service.java b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GmmService.java
similarity index 62%
rename from src/main/java/gov/usgs/earthquake/nshmp/www/gmm/Service.java
rename to src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GmmService.java
index 8a0d70495913d7db99046d6b74db8a5c218808f1..a75c69ad05693bfbd355df2138f7022dcf4b42a8 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/Service.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GmmService.java
@@ -6,20 +6,17 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-
 import gov.usgs.earthquake.nshmp.Maths;
 import gov.usgs.earthquake.nshmp.data.DoubleData;
 import gov.usgs.earthquake.nshmp.data.XySequence;
 import gov.usgs.earthquake.nshmp.gmm.Gmm;
 import gov.usgs.earthquake.nshmp.gmm.GmmInput;
-import gov.usgs.earthquake.nshmp.gmm.GmmInput.Constraints;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
-import gov.usgs.earthquake.nshmp.www.WsUtils;
+import gov.usgs.earthquake.nshmp.www.ResponseBody;
 import gov.usgs.earthquake.nshmp.www.gmm.ResponseSpectra.GmmData;
 import gov.usgs.earthquake.nshmp.www.gmm.XYDataGroup.EpiSeries;
 
@@ -28,33 +25,13 @@ import io.micronaut.http.HttpResponse;
 import jakarta.inject.Singleton;
 
 @Singleton
-public class Service {
-
-  static final Gson GSON;
-
-  static {
-    GSON = new GsonBuilder()
-        .setPrettyPrinting()
-        .serializeNulls()
-        .disableHtmlEscaping()
-        .registerTypeAdapter(Double.class, new WsUtils.NaNSerializer())
-        .registerTypeAdapter(GmmServices.Parameters.class,
-            new GmmServices.Parameters.Serializer())
-        .registerTypeAdapter(Imt.class, new WsUtils.EnumSerializer<Imt>())
-        .registerTypeAdapter(Constraints.class, new WsUtils.ConstraintsSerializer())
-        .create();
-  }
-
-  public static HttpResponse<String> metadata(HttpRequest<?> request, Id service) {
-    var metadata = GSON.toJson(GmmServices.getMetadata(request, service));
-    return HttpResponse.ok(metadata);
-  }
+public class GmmService {
 
   /* Base request object for all GMM services. */
   public static class Request {
 
-    transient HttpRequest<?> http;
-    transient Id serviceId;
+    transient final HttpRequest<?> http;
+    transient final Id serviceId;
 
     final Set<Gmm> gmms;
     final GmmInput input;
@@ -69,12 +46,31 @@ public class Service {
     }
   }
 
-  static class SpectraResponse {
+  static class Spectra {
+
+    static HttpResponse<String> processRequestSpectra(
+        GmmService.Request request) {
+
+      Map<Gmm, GmmData> spectra = ResponseSpectra.spectra(request.gmms, request.input, false);
+      Response response = Response.create(request.serviceId, spectra);
+      var body = ResponseBody.success()
+          .name(request.serviceId.name)
+          .url(request.http.getUri().getPath())
+          .request(request)
+          .response(response)
+          .build();
+      String json = GmmServices.GSON.toJson(body);
+      return HttpResponse.ok(json);
+    }
+
+  }
+
+  static class Response {
 
     XYDataGroup means;
     XYDataGroup sigmas;
 
-    private SpectraResponse(Id service) {
+    private Response(Id service) {
       means = XYDataGroup.create(
           service.groupNameMean,
           service.xLabel,
@@ -86,14 +82,16 @@ public class Service {
           service.yLabelSigma);
     }
 
-    static SpectraResponse create(
+    static Response create(
         Id service,
         Map<Gmm, GmmData> result) {
 
-      return new SpectraResponse(service).setXY(result);
+      Response response = new Response(service);
+      response.setXY(result);
+      return response;
     }
 
-    private SpectraResponse setXY(Map<Gmm, GmmData> result) {
+    private void setXY(Map<Gmm, GmmData> result) {
 
       for (Entry<Gmm, GmmData> entry : result.entrySet()) {
         Gmm gmm = entry.getKey();
@@ -133,11 +131,127 @@ public class Service {
         means.add(gmm.name(), gmm.toString(), μTotal, meanEpi);
         sigmas.add(gmm.name(), gmm.toString(), σTotal, sigmaEpi);
       }
-      return this;
+    }
+  }
+
+  static class Distance {
+
+    private static final double R_MIN = -100.0;
+    private static final double R_MAX = 100.0;
+    private final static int R_POINTS = 100;
+
+    static HttpResponse<String> processRequestDistance(
+        GmmService.Distance.Request request) {
+
+      double[] rArray = distanceArray(request);
+
+      Map<Gmm, GmmData> gmvr = GroundMotionData.versusDistance2(
+          request.gmms,
+          request.input,
+          request.imt,
+          rArray);
+      Response response = Response.create(request.serviceId, gmvr);
+      var body = ResponseBody.success()
+          .name(request.serviceId.name)
+          .url(request.http.getUri().getPath())
+          .request(request)
+          .response(response)
+          .build();
+      String json = GmmServices.GSON.toJson(body);
+      return HttpResponse.ok(json);
+    }
+
+    private static double[] distanceArray(
+        GmmService.Distance.Request request) {
+      var isLog = request.serviceId.equals(Id.DISTANCE) ? true : false;
+      var rStep = isLog
+          ? (Math.log10(request.rMax / request.rMin)) / (R_POINTS - 1)
+          : 1.0;
+      return isLog
+          ? GmmServices.sequenceLog(request.rMin, request.rMax, rStep)
+          : GmmServices.sequenceLinear(request.rMin, request.rMax, rStep);
+    }
+
+    static class Request extends GmmService.Request {
+
+      Imt imt;
+      double rMin;
+      double rMax;
+
+      Request(
+          HttpRequest<?> http, GmmService.Id serviceId,
+          Set<Gmm> gmms, GmmInput in,
+          Optional<Imt> imt,
+          Optional<Double> rMin,
+          Optional<Double> rMax) {
+
+        super(http, serviceId, gmms, in);
+        this.imt = imt.orElse(Imt.PGA);
+        this.rMin = rMin.orElse(R_MIN);
+        this.rMax = rMax.orElse(R_MAX);
+      }
+    }
+  }
+
+  static class Magnitude {
+
+    public static final double M_MIN = 5.0;
+    public static final double M_MAX = 8.0;
+    public static final double M_STEP = 0.1;
+    public static final double M_DISTANCE = 10.0;
+
+    static HttpResponse<String> processRequestMagnitude(
+        GmmService.Magnitude.Request request) {
+
+      double[] mArray = GmmServices.sequenceLinear(
+          request.mMin, request.mMax, request.step);
+
+      Map<Gmm, GmmData> gmvm = GroundMotionData.versusMagnitude2(
+          request.gmms,
+          request.input,
+          request.imt,
+          mArray,
+          request.distance);
+      Response response = Response.create(request.serviceId, gmvm);
+      var body = ResponseBody.success()
+          .name(request.serviceId.name)
+          .url(request.http.getUri().getPath())
+          .request(request)
+          .response(response)
+          .build();
+      String json = GmmServices.GSON.toJson(body);
+      return HttpResponse.ok(json);
     }
 
+    static class Request extends GmmService.Request {
+
+      Imt imt;
+      double mMin;
+      double mMax;
+      double step;
+      double distance;
+
+      Request(
+          HttpRequest<?> http, GmmService.Id serviceId,
+          Set<Gmm> gmms, GmmInput in,
+          Optional<Imt> imt,
+          Optional<Double> mMin,
+          Optional<Double> mMax,
+          Optional<Double> step,
+          Optional<Double> distance) {
+
+        super(http, serviceId, gmms, in);
+        this.imt = imt.orElse(Imt.PGA);
+        this.mMin = mMin.orElse(M_MIN);
+        this.mMax = mMax.orElse(M_MAX);
+        this.step = step.orElse(M_STEP);
+        this.distance = distance.orElse(M_DISTANCE);
+      }
+    }
   }
 
+  /* ***********************************************/
+
   /*
    * Consolidators of epi branches. GMMs return logic trees that represent all
    * combinations of means and sigmas. For the response spectra service we want
@@ -185,12 +299,13 @@ public class Service {
   }
 
   // currently used for distance and magnitude
-  static class Response {
+  @Deprecated
+  static class Response2 {
 
     XYDataGroup means;
     XYDataGroup sigmas;
 
-    private Response(Id service) {
+    private Response2(Id service) {
       means = XYDataGroup.create(
           service.groupNameMean,
           service.xLabel,
@@ -202,18 +317,19 @@ public class Service {
           service.yLabelSigma);
     }
 
-    static Response create(
+    static Response2 create(
         Id service,
-        Request request,
-        GroundMotions result) {
+        GroundMotionData result) {
 
-      return new Response(service).setXY(
+      Response2 response = new Response2(service);
+      response.setXY(
           result.xs(),
           result.means(),
           result.sigmas());
+      return response;
     }
 
-    private Response setXY(
+    private Response2 setXY(
         Map<Gmm, List<Double>> xs,
         Map<Gmm, List<Double>> means,
         Map<Gmm, List<Double>> sigmas) {
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GmmServices.java b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GmmServices.java
index b178ba7cc6ec3e9e4a73376e8fa055ce81b35b6f..5db6efd4e0e5621b9ac988f870fe0b84f2374b54 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GmmServices.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GmmServices.java
@@ -12,12 +12,12 @@ import static gov.usgs.earthquake.nshmp.gmm.GmmInput.Field.WIDTH;
 import static gov.usgs.earthquake.nshmp.gmm.GmmInput.Field.Z1P0;
 import static gov.usgs.earthquake.nshmp.gmm.GmmInput.Field.Z2P5;
 import static gov.usgs.earthquake.nshmp.gmm.GmmInput.Field.ZHYP;
-import static gov.usgs.earthquake.nshmp.gmm.GmmInput.Field.ZTOP;
+import static gov.usgs.earthquake.nshmp.gmm.GmmInput.Field.ZTOR;
 import static gov.usgs.earthquake.nshmp.gmm.Imt.AI;
 import static gov.usgs.earthquake.nshmp.gmm.Imt.PGV;
 import static io.micronaut.core.type.Argument.BOOLEAN;
 import static io.micronaut.core.type.Argument.DOUBLE;
-import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toCollection;
 
 import java.lang.reflect.Type;
 import java.util.ArrayList;
@@ -31,6 +31,8 @@ import java.util.stream.Collectors;
 
 import com.google.common.collect.Range;
 import com.google.common.primitives.Doubles;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonSerializationContext;
@@ -44,11 +46,13 @@ import gov.usgs.earthquake.nshmp.gmm.GmmInput.Constraints;
 import gov.usgs.earthquake.nshmp.gmm.GmmInput.Field;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
-import gov.usgs.earthquake.nshmp.www.gmm.Service.Id;
+import gov.usgs.earthquake.nshmp.www.WsUtils;
+import gov.usgs.earthquake.nshmp.www.gmm.GmmService.Id;
 import gov.usgs.earthquake.nshmp.www.meta.EnumParameter;
 
 import io.micronaut.http.HttpParameters;
 import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
 
 class GmmServices {
 
@@ -58,6 +62,26 @@ class GmmServices {
   static final double MAGNITUDE_DEFAULT_DISTANCE = 10.0;
   static final Range<Double> DISTANCE_DEFAULT_VALUES = Range.closed(-100.0, 100.0);
 
+  static final Gson GSON;
+
+  static {
+    GSON = new GsonBuilder()
+        .setPrettyPrinting()
+        .serializeNulls()
+        .disableHtmlEscaping()
+        .registerTypeAdapter(Double.class, new WsUtils.NaNSerializer())
+        .registerTypeAdapter(GmmServices.Parameters.class,
+            new GmmServices.Parameters.Serializer())
+        .registerTypeAdapter(Imt.class, new WsUtils.EnumSerializer<Imt>())
+        .registerTypeAdapter(Constraints.class, new WsUtils.ConstraintsSerializer())
+        .create();
+  }
+
+  public static HttpResponse<String> metadata(HttpRequest<?> request, Id service) {
+    var metadata = GSON.toJson(GmmServices.getMetadata(request, service));
+    return HttpResponse.ok(metadata);
+  }
+
   /** Query and JSON reqest/response keys. */
   static final class Key {
     public static final String DISTANCE = "distance";
@@ -91,7 +115,7 @@ class GmmServices {
     params.getFirst(RX.id, DOUBLE).ifPresent(b::rX);
     params.getFirst(DIP.id, DOUBLE).ifPresent(b::dip);
     params.getFirst(WIDTH.id, DOUBLE).ifPresent(b::width);
-    params.getFirst(ZTOP.id, DOUBLE).ifPresent(b::zTop);
+    params.getFirst(ZTOR.id, DOUBLE).ifPresent(b::zTor);
     params.getFirst(ZHYP.id, DOUBLE).ifPresent(b::zHyp);
     params.getFirst(RAKE.id, DOUBLE).ifPresent(b::rake);
     params.getFirst(VS30.id, DOUBLE).ifPresent(b::vs30);
@@ -102,18 +126,14 @@ class GmmServices {
   }
 
   /* Read the 'gmm' query values. */
-  static Optional<Set<Gmm>> readGmms(HttpRequest<?> request) {
-    List<Gmm> gmmList = request.getParameters()
+  static Set<Gmm> readGmms(HttpRequest<?> request) {
+    return request.getParameters()
         .getAll(Key.GMM)
         .stream()
         .map(s -> s.split(","))
         .flatMap(Arrays::stream)
         .map(Gmm::valueOf)
-        .collect(toList());
-
-    return gmmList.isEmpty()
-        ? Optional.empty()
-        : Optional.of(EnumSet.copyOf(gmmList));
+        .collect(toCollection(() -> EnumSet.noneOf(Gmm.class)));
   }
 
   static ResponseBody<String, MetadataResponse> getMetadata(
@@ -372,7 +392,7 @@ class GmmServices {
       Value(Gmm gmm) {
         this.id = gmm.name();
         this.label = gmm.toString();
-        this.supportedImts = SupportedImts(gmm.supportedIMTs());
+        this.supportedImts = SupportedImts(gmm.supportedImts());
         this.constraints = gmm.constraints();
       }
     }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GroundMotions.java b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GroundMotionData.java
similarity index 60%
rename from src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GroundMotions.java
rename to src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GroundMotionData.java
index 947c7b716e63f11a0e5a3f23125be76d0fd49a63..855788ce8f96b9dd866dfa9b5728c1dd6437cf2d 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GroundMotions.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GroundMotionData.java
@@ -9,6 +9,7 @@ import static java.lang.Math.toRadians;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -18,22 +19,21 @@ import java.util.stream.Collectors;
 import com.google.common.primitives.Doubles;
 
 import gov.usgs.earthquake.nshmp.Maths;
-import gov.usgs.earthquake.nshmp.data.DoubleData;
 import gov.usgs.earthquake.nshmp.gmm.Gmm;
 import gov.usgs.earthquake.nshmp.gmm.GmmInput;
 import gov.usgs.earthquake.nshmp.gmm.GroundMotion;
 import gov.usgs.earthquake.nshmp.gmm.GroundMotionModel;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
-import gov.usgs.earthquake.nshmp.tree.Branch;
 import gov.usgs.earthquake.nshmp.tree.LogicTree;
+import gov.usgs.earthquake.nshmp.www.gmm.ResponseSpectra.GmmData;
 
-class GroundMotions {
+class GroundMotionData {
 
   private TreeMap<Gmm, List<Double>> means;
   private TreeMap<Gmm, List<Double>> sigmas;
   private TreeMap<Gmm, List<Double>> xs;
 
-  private GroundMotions(
+  private GroundMotionData(
       TreeMap<Gmm, List<Double>> means,
       TreeMap<Gmm, List<Double>> sigmas,
       TreeMap<Gmm, List<Double>> xs) {
@@ -50,13 +50,30 @@ class GroundMotions {
    * @param imt The intensity measure type
    * @param distance The distances to calculate ground motions
    */
-  static GroundMotions versusDistance(
+  static GroundMotionData versusDistance(
       Set<Gmm> gmms,
       GmmInput inputModel,
       Imt imt,
-      double[] distance) {
-    var gmmInputList = hangingWallDistances(inputModel, distance);
-    return calculateGroundMotions(gmms, gmmInputList, imt, distance);
+      double[] distances) {
+    var gmmInputList = hangingWallDistances(inputModel, distances);
+    return calculateGroundMotions(gmms, gmmInputList, imt, distances);
+  }
+
+  /**
+   * Calculate ground motion VS distance.
+   *
+   * @param gmms The GMMs
+   * @param inputModel The GMM input
+   * @param imt The intensity measure type
+   * @param distance The distances to calculate ground motions
+   */
+  static Map<Gmm, GmmData> versusDistance2(
+      Set<Gmm> gmms,
+      GmmInput inputModel,
+      Imt imt,
+      double[] distances) {
+    var gmmInputList = hangingWallDistances(inputModel, distances);
+    return calculateGroundMotions2(gmms, gmmInputList, imt, distances);
   }
 
   /**
@@ -68,7 +85,7 @@ class GroundMotions {
    * @param magnitudes The magnitudes to calculate the ground motions
    * @param distance The distance to calculate ground motions
    */
-  static GroundMotions versusMagnitude(
+  static GroundMotionData versusMagnitude(
       Set<Gmm> gmms,
       GmmInput inputModel,
       Imt imt,
@@ -82,6 +99,29 @@ class GroundMotions {
     return calculateGroundMotions(gmms, gmmInputs, imt, magnitudes);
   }
 
+  /**
+   * Calculate ground motion VS magnitude.
+   *
+   * @param gmms The GMMs
+   * @param inputModel The GMM input
+   * @param imt The intensity measure type
+   * @param magnitudes The magnitudes to calculate the ground motions
+   * @param distance The distance to calculate ground motions
+   */
+  static Map<Gmm, GmmData> versusMagnitude2(
+      Set<Gmm> gmms,
+      GmmInput inputModel,
+      Imt imt,
+      double[] magnitudes,
+      double distance) {
+    var gmmInputs = Arrays.stream(magnitudes)
+        .mapToObj(Mw -> GmmInput.builder().fromCopy(inputModel).mag(Mw).build())
+        .map(gmmInput -> hangingWallDistance(gmmInput, distance))
+        .collect(Collectors.toList());
+
+    return calculateGroundMotions2(gmms, gmmInputs, imt, magnitudes);
+  }
+
   Map<Gmm, List<Double>> means() {
     return Collections.unmodifiableMap(this.means);
   }
@@ -94,7 +134,7 @@ class GroundMotions {
     return Collections.unmodifiableMap(this.xs);
   }
 
-  private static GroundMotions calculateGroundMotions(
+  private static GroundMotionData calculateGroundMotions(
       Set<Gmm> gmms,
       List<GmmInput> gmmInputs,
       Imt imt,
@@ -109,7 +149,8 @@ class GroundMotions {
 
       GroundMotionModel model = gmm.instance(imt);
       for (GmmInput gmmInput : gmmInputs) {
-        GroundMotion gm = combine(model.calc(gmmInput));
+        GroundMotion gm = gov.usgs.earthquake.nshmp.gmm.GroundMotions.combine(
+            model.calc(gmmInput));
         means.add(gm.mean());
         sigmas.add(gm.sigma());
       }
@@ -119,34 +160,54 @@ class GroundMotions {
       xsMap.put(gmm, Doubles.asList(distance));
     }
 
-    return new GroundMotions(
+    return new GroundMotionData(
         meanMap,
         sigmaMap,
         xsMap);
   }
 
-  @Deprecated
-  static GroundMotion combine(LogicTree<GroundMotion> tree) {
-    // once GroundMotions in lib is exposed remove this
-    if (tree.size() == 1) {
-      return tree.get(0).value();
+  private static Map<Gmm, GmmData> calculateGroundMotions2(
+      Set<Gmm> gmms,
+      List<GmmInput> gmmInputs,
+      Imt imt,
+      double[] distances) {
+
+    // var xsMap = new TreeMap<Gmm, List<Double>>();
+    // var meanMap = new TreeMap<Gmm, List<Double>>();
+    // var sigmaMap = new TreeMap<Gmm, List<Double>>();
+
+    Map<Gmm, GmmData> gmValues = new EnumMap<>(Gmm.class);
+
+    for (Gmm gmm : gmms) {
+
+      List<LogicTree<GroundMotion>> trees = new ArrayList<>();
+
+      // var means = new ArrayList<Double>();
+      // var sigmas = new ArrayList<Double>();
+
+      GroundMotionModel model = gmm.instance(imt);
+
+      for (GmmInput gmmInput : gmmInputs) {
+
+        trees.add(model.calc(gmmInput));
+
+        // GroundMotion gm = GroundMotions.combine(model.calc(gmmInput));
+        // means.add(gm.mean());
+        // sigmas.add(gm.sigma());
+      }
+
+      GmmData dataGroup = ResponseSpectra.treesToDataGroup(Doubles.asList(distances), trees);
+      gmValues.put(gmm, dataGroup);
+      // meanMap.put(gmm, List.copyOf(means));
+      // sigmaMap.put(gmm, List.copyOf(sigmas));
+      // xsMap.put(gmm, Doubles.asList(distance));
     }
-    double[] means = tree.stream()
-        .map(Branch::value)
-        .mapToDouble(GroundMotion::mean)
-        .toArray();
-    double[] sigmas = tree.stream()
-        .map(Branch::value)
-        .mapToDouble(GroundMotion::sigma)
-        .toArray();
-    double[] weights = tree.stream()
-        .mapToDouble(Branch::weight)
-        .toArray();
-    double mean = DoubleData.weightedSumLn(means, weights);
-    double sigma = DoubleData.weightedSum(sigmas, weights);
-    // TODO update NGA-East test results
-    // double sigma = Maths.srssWeighted(sigmas, weights);
-    return GroundMotion.create(mean, sigma);
+
+    return gmValues;
+    // return new GroundMotionData(
+    // meanMap,
+    // sigmaMap,
+    // xsMap);
   }
 
   /*
@@ -161,19 +222,19 @@ class GroundMotions {
     double v = sin(δ) * inputModel.width;
 
     /* Depth to bottom of rupture */
-    double zBot = inputModel.zTop + v;
+    double zBot = inputModel.zTor + v;
 
     /* Distance range over which site is normal to fault plane */
-    double rCutLo = tan(δ) * inputModel.zTop;
+    double rCutLo = tan(δ) * inputModel.zTor;
     double rCutHi = tan(δ) * zBot + h;
 
     /* rRup values corresponding to cutoffs above */
-    double rRupLo = Maths.hypot(inputModel.zTop, rCutLo);
+    double rRupLo = Maths.hypot(inputModel.zTor, rCutLo);
     double rRupHi = Maths.hypot(zBot, rCutHi - h);
 
     double rJB = (r < 0) ? -r : (r < h) ? 0.0 : r - h;
     double rRup = (r < rCutLo)
-        ? hypot(r, inputModel.zTop)
+        ? hypot(r, inputModel.zTor)
         : (r > rCutHi)
             ? hypot(r - h, zBot)
             : rRupScaled(
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/MagnitudeService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/MagnitudeService.java
deleted file mode 100644
index 83ffef9ca011e1d2c9f496755238efe412500e6d..0000000000000000000000000000000000000000
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/MagnitudeService.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package gov.usgs.earthquake.nshmp.www.gmm;
-
-import java.util.Optional;
-import java.util.Set;
-
-import gov.usgs.earthquake.nshmp.gmm.Gmm;
-import gov.usgs.earthquake.nshmp.gmm.GmmInput;
-import gov.usgs.earthquake.nshmp.gmm.Imt;
-import gov.usgs.earthquake.nshmp.www.ResponseBody;
-import gov.usgs.earthquake.nshmp.www.gmm.Service.Response;
-
-import io.micronaut.http.HttpRequest;
-import io.micronaut.http.HttpResponse;
-import jakarta.inject.Singleton;
-
-@Singleton
-class MagnitudeService {
-
-  public static final double M_MIN = 5.0;
-  public static final double M_MAX = 8.0;
-  public static final double M_STEP = 0.1;
-  public static final double M_DISTANCE = 10.0;
-
-  static HttpResponse<String> processRequestMagnitude(
-      MagnitudeService.Request request) {
-
-    var magArray = GmmServices.sequenceLinear(
-        request.mMin, request.mMax, request.step);
-    var gmvm = GroundMotions.versusMagnitude(
-        request.gmms,
-        request.input,
-        request.imt,
-        magArray,
-        request.distance);
-    var response = Response.create(request.serviceId, request, gmvm);
-    var body = ResponseBody.success()
-        .name(request.serviceId.name)
-        .url(request.http.getUri().getPath())
-        .request(request)
-        .response(response)
-        .build();
-    var json = Service.GSON.toJson(body);
-    return HttpResponse.ok(json);
-  }
-
-  static class Request extends Service.Request {
-
-    Imt imt;
-    double mMin;
-    double mMax;
-    double step;
-    double distance;
-
-    Request(
-        HttpRequest<?> http, Service.Id serviceId,
-        Set<Gmm> gmms, GmmInput in,
-        Optional<Imt> imt,
-        Optional<Double> mMin,
-        Optional<Double> mMax,
-        Optional<Double> step,
-        Optional<Double> distance) {
-
-      super(http, serviceId, gmms, in);
-      this.imt = imt.orElse(Imt.PGA);
-      this.mMin = mMin.orElse(M_MIN);
-      this.mMax = mMax.orElse(M_MAX);
-      this.step = step.orElse(M_STEP);
-      this.distance = distance.orElse(M_DISTANCE);
-    }
-  }
-}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/ResponseSpectra.java b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/ResponseSpectra.java
index 44da5982fe8a2d588bd8e5c9ec70faf27ea3d9b7..e7fec23c87606a9dfb7316970642f8d9163263bc 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/ResponseSpectra.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/ResponseSpectra.java
@@ -12,6 +12,7 @@ import com.google.common.collect.ImmutableList;
 import gov.usgs.earthquake.nshmp.gmm.Gmm;
 import gov.usgs.earthquake.nshmp.gmm.GmmInput;
 import gov.usgs.earthquake.nshmp.gmm.GroundMotion;
+import gov.usgs.earthquake.nshmp.gmm.GroundMotions;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.tree.Branch;
 import gov.usgs.earthquake.nshmp.tree.LogicTree;
@@ -50,7 +51,7 @@ class ResponseSpectra {
      */
 
     /* Common imts and periods; may not be used. */
-    Set<Imt> saImts = Gmm.responseSpectrumIMTs(gmms);
+    Set<Imt> saImts = Gmm.responseSpectrumImts(gmms);
     List<Double> periods = new ArrayList<>();
     periods.add(PGA_PERIOD);
     periods.addAll(Imt.periods(saImts));
@@ -58,7 +59,7 @@ class ResponseSpectra {
     Map<Gmm, GmmData> gmmSpectra = new EnumMap<>(Gmm.class);
     for (Gmm gmm : gmms) {
       if (!commonImts) {
-        saImts = gmm.responseSpectrumIMTs();
+        saImts = gmm.responseSpectrumImts();
         periods = ImmutableList.<Double> builder()
             .add(PGA_PERIOD)
             .addAll(Imt.periods(saImts))
@@ -143,7 +144,7 @@ class ResponseSpectra {
 
   static class GmmData {
 
-    final double[] periods;
+    final double[] periods; // should be any x-value
     final double[] μs;
     final double[] σs;
     final List<EpiBranch> tree;
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/SpectraService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/SpectraService.java
deleted file mode 100644
index 8fff43eb009a5f56880fdb28eede02e0c44c20fb..0000000000000000000000000000000000000000
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/SpectraService.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package gov.usgs.earthquake.nshmp.www.gmm;
-
-import gov.usgs.earthquake.nshmp.www.ResponseBody;
-import gov.usgs.earthquake.nshmp.www.gmm.Service.SpectraResponse;
-
-import io.micronaut.http.HttpResponse;
-import jakarta.inject.Singleton;
-
-@Singleton
-public class SpectraService {
-
-  public static HttpResponse<String> processRequestSpectra(
-      Service.Request request) {
-
-    var spectra = ResponseSpectra.spectra(request.gmms, request.input, false);
-    var response = SpectraResponse.create(request.serviceId, spectra);
-    var body = ResponseBody.success()
-        .name(request.serviceId.name)
-        .url(request.http.getUri().getPath())
-        .request(request)
-        .response(response)
-        .build();
-    var json = Service.GSON.toJson(body);
-    return HttpResponse.ok(json);
-  }
-}