diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/Application.java b/src/main/java/gov/usgs/earthquake/nshmp/www/Application.java
index 2e03a77585d401e7dd58daec1004b1ea8d8beb1a..4c63d414bf45605da62684432d262eb8e75afdbf 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/Application.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/Application.java
@@ -6,9 +6,8 @@ import io.swagger.v3.oas.annotations.info.Info;
 
 @OpenAPIDefinition(
     info = @Info(
-        title = "NSHMP NSHM Services",
-        description = "### Services related to National Seismic Hazard Models:\n" +
-            "* Source Model: Get the metadata about current installed NSHM"))
+        title = "USGS NSHM Services",
+        description = "National Seismic Hazard Model (NSHM) hazard calculations and queries."))
 public class Application {
 
   public static void main(String[] args) {
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/DeaggEpsilonController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/DeaggEpsilonController.java
index 478f8087ad759a3d4acd890ab3f437cdd0ecd4b1..592309fb31cdbff3b2e2f6da6c91c2ba28d9ffab 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/DeaggEpsilonController.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/DeaggEpsilonController.java
@@ -33,7 +33,7 @@ public class DeaggEpsilonController {
   @Get(uri = "/usage", produces = MediaType.APPLICATION_JSON)
   public HttpResponse<String> doGetUsage(HttpRequest<?> request) {
     var urlHelper = servlet.urlHelper(request);
-    return HazardService.handleDoGetUsage(urlHelper);
+    return HazardService.handleDoGetMetadata(urlHelper);
   }
 
   /**
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/HazardController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/HazardController.java
index d678f3a50bf0d8f62addb22fb17ad04777a6aa30..d6d381b185c5effae7ca16267f330471439471a2 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/HazardController.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/HazardController.java
@@ -1,7 +1,5 @@
 package gov.usgs.earthquake.nshmp.www;
 
-import java.util.Optional;
-
 import javax.annotation.Nullable;
 import javax.inject.Inject;
 
@@ -11,7 +9,6 @@ import gov.usgs.earthquake.nshmp.www.services.HazardService.QueryParameters;
 
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
-import io.micronaut.http.MediaType;
 import io.micronaut.http.annotation.Controller;
 import io.micronaut.http.annotation.Get;
 import io.micronaut.http.annotation.PathVariable;
@@ -25,104 +22,59 @@ import io.swagger.v3.oas.annotations.tags.Tag;
  * Micronaut controller for probabilistic seismic hazard calculations.
  *
  * @see HazardService
- *
  * @author U.S. Geological Survey
  */
 @Tag(
-    name = "Hazard Service",
-    description = "USGS NSHMP Hazard Curve Calculator")
+    name = "Hazard",
+    description = "USGS NSHMP hazard calculation service")
 @Controller("/hazard")
 public class HazardController {
 
   @Inject
   private NshmpMicronautServlet servlet;
 
-  /**
-   * GET method to return the hazard usage.
-   *
-   * @param request The HTTP request
-   */
   @Operation(
-      summary = "Returns the hazard service usage",
-      description = "Returns the installed model and supported:\n" +
-          "* Region bounds\n * Return periods\n * Vs30s",
-      operationId = "hazard_doGetUsage")
+      summary = "Hazard model and service metadata",
+      description = "Returns details of the installed model and service request parameters",
+      operationId = "hazard_doGetMetadata")
   @ApiResponse(
-      description = "Hazard usage",
+      description = "Hazard service metadata",
       responseCode = "200")
-  @Get(uri = "/usage", produces = MediaType.APPLICATION_JSON)
-  public HttpResponse<String> doGetUsage(HttpRequest<?> request) {
+  @Get
+  public HttpResponse<String> doGetMetadata(HttpRequest<?> request) {
     var urlHelper = servlet.urlHelper(request);
-    return HazardService.handleDoGetUsage(urlHelper);
+    return HazardService.handleDoGetMetadata(urlHelper);
   }
 
   /**
-   * GET method to return usage or hazard curves, query based.
-   *
-   * @param request The HTTP request
-   * @param longitude Longitude (in decimal degrees) ([-360, 360])
-   * @param latitude Latitude (in decimal degrees) ([-90, 90])
-   * @param vs30 The site soil class
+   * @param longitude Longitude in decimal degrees [-360..360]
+   * @param latitude Latitude in decimal degrees [-90..90]
+   * @param vs30 Site Vs30 value in m/s [150..3000]
+   * @param truncate Truncate curves at return periods below ~10,000 years
+   * @param maxdir Apply max-direction scaling
    */
   @Operation(
-      summary = "Compute hazards",
-      description = "Compute hazard curves given longitude, latitude, and Vs30",
+      summary = "Compute probabilisitic hazard at a site",
+      description = "Returns hazard curves computed from the installed model",
       operationId = "hazard_doGetHazard")
   @ApiResponse(
       description = "Hazard curves",
       responseCode = "200")
-  @Get // (uri = "{?longitude,latitude,vs30}")
+  @Get(uri = "/{longitude}/{latitude}/{vs30}{?truncate,maxdir}")
   public HttpResponse<String> doGetHazard(
       HttpRequest<?> request,
-      @Schema(
-          required = true,
-          minimum = "-360",
-          maximum = "360") @QueryValue @Nullable Optional<Double> longitude,
-      @Schema(
-          required = true,
-          minimum = "-90",
-          maximum = "90") @QueryValue @Nullable Optional<Double> latitude,
-      @Schema(
-          required = true) @QueryValue @Nullable Optional<Integer> vs30) {
+      @Schema(minimum = "-360", maximum = "360") @PathVariable double longitude,
+      @Schema(minimum = "-90", maximum = "90") @PathVariable double latitude,
+      @Schema(minimum = "150", maximum = "3000") @PathVariable int vs30,
+      @QueryValue(defaultValue = "false") @Nullable boolean truncate,
+      @QueryValue(defaultValue = "false") @Nullable boolean maxdir) {
 
-    var query = new QueryParameters(longitude, latitude, vs30);
-    return HazardService.handleDoGetHazard(
-        query,
-        servlet.urlHelper(request));
-  }
-
-  /**
-   * GET method to return usage or hazard curves, slash based.
-   *
-   * @param request The HTTP request
-   * @param longitude Longitude (in decimal degrees) ([-360, 360])
-   * @param latitude Latitude (in decimal degrees) ([-90, 90])
-   * @param vs30 The site soil class
-   */
-  @Operation(
-      summary = "Compute hazards",
-      description = "Compute hazard curves given longitude, latitude, and Vs30",
-      operationId = "hazard_doGetHazardSlash")
-  @ApiResponse(
-      description = "Hazard curves",
-      responseCode = "200")
-  @Get(uri = "{/longitude}{/latitude}{/vs30}")
-  public HttpResponse<String> doGetHazardSlash(
-      HttpRequest<?> request,
-      @Schema(
-          required = true,
-          minimum = "-360",
-          maximum = "360") @PathVariable @Nullable Optional<Double> longitude,
-      @Schema(
-          required = true,
-          minimum = "-90",
-          maximum = "90") @PathVariable @Nullable Optional<Double> latitude,
-      @Schema(
-          required = true) @PathVariable @Nullable Optional<Integer> vs30) {
+    /*
+     * @Schema annotation parameter constraints only affect Swagger service
+     * index page behavior; still need to validate against model. TODO
+     */
 
-    var query = new QueryParameters(longitude, latitude, vs30);
-    return HazardService.handleDoGetHazard(
-        query,
-        servlet.urlHelper(request));
+    var query = new QueryParameters(longitude, latitude, vs30, truncate, maxdir);
+    return HazardService.handleDoGetHazard(query, servlet.urlHelper(request));
   }
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/DeaggEpsilonService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/DeaggEpsilonService.java
index 02cb1f53bbed3603427b5e0c6576f204b7abfca4..a2b851e62fa1a30745514ec4503dc48fffde7611 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/DeaggEpsilonService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/DeaggEpsilonService.java
@@ -54,7 +54,7 @@ public final class DeaggEpsilonService {
       var timer = ServletUtil.timer();
 
       if (query.isNull()) {
-        return HazardService.handleDoGetUsage(urlHelper);
+        return HazardService.handleDoGetMetadata(urlHelper);
       }
 
       query.checkValues();
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService.java
index 8c40a32cae677492dad8c014ed8f6945daf0dd09..8405bbdc7626a1c27a24f23260d3b4fc41f33b9a 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService.java
@@ -1,14 +1,13 @@
 package gov.usgs.earthquake.nshmp.www.services;
 
-import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static gov.usgs.earthquake.nshmp.calc.HazardExport.curvesBySource;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Function;
 
@@ -46,22 +45,10 @@ import io.micronaut.http.HttpResponse;
  */
 public final class HazardService {
 
-  /*
-   * Developer notes:
-   *
-   * Calculations are designed to leverage all available processors by default,
-   * distributing work using the ServletUtil.CALC_EXECUTOR.
-   */
-
   private static final String NAME = "Hazard Service";
 
-  /**
-   * Handler for {@link HazardController#doGetUsage}. Returns the usage for the
-   * hazard service.
-   *
-   * @param urlHelper The URL helper
-   */
-  public static HttpResponse<String> handleDoGetUsage(UrlHelper urlHelper) {
+  /** HazardController.doGetUsage() handler. */
+  public static HttpResponse<String> handleDoGetMetadata(UrlHelper urlHelper) {
     try {
       var usage = new RequestMetadata(ServletUtil.model());// SourceServices.ResponseData();
       var response = new Response<>(Status.USAGE, NAME, urlHelper.url, usage, urlHelper);
@@ -72,22 +59,17 @@ public final class HazardService {
     }
   }
 
-  /**
-   * Handler for {@link HazardController#doGetHazard}. Returns the usage or the
-   * hazard result.
-   *
-   * @param query The query
-   * @param urlHelper The URL helper
-   */
+  /** HazardController.doGetHazard() handler. */
   public static HttpResponse<String> handleDoGetHazard(
       QueryParameters query,
       UrlHelper urlHelper) {
 
     try {
-      if (query.isEmpty()) {
-        return handleDoGetUsage(urlHelper);
-      }
-      query.checkParameters();
+      // TODO still need to validate
+      // if (query.isEmpty()) {
+      // return handleDoGetUsage(urlHelper);
+      // }
+      // query.checkParameters();
       var data = new RequestData(query);
       var response = process(data, urlHelper);
       var svcResponse = ServletUtil.GSON.toJson(response);
@@ -142,35 +124,37 @@ public final class HazardService {
 
   public static class QueryParameters {
 
-    Optional<Double> longitude;
-    Optional<Double> latitude;
-    Optional<Integer> vs30;
+    final double longitude;
+    final double latitude;
+    final int vs30;
+    final boolean truncate;
+    final boolean maxdir;
 
     public QueryParameters(
-        Optional<Double> longitude,
-        Optional<Double> latitude,
-        Optional<Integer> vs30) {
+        double longitude,
+        double latitude,
+        int vs30,
+        boolean truncate,
+        boolean maxdir) {
 
       this.longitude = longitude;
       this.latitude = latitude;
       this.vs30 = vs30;
+      this.truncate = truncate;
+      this.maxdir = maxdir;
     }
 
-    public boolean isEmpty() {
-      return longitude.isEmpty() && latitude.isEmpty() && vs30.isEmpty();
-    }
-
-    public void checkParameters() {
-      checkParameter(longitude, "longitude");
-      checkParameter(latitude, "latitude");
-      checkParameter(vs30, "vs30");
-    }
+    // void checkParameters() {
+    // checkParameter(longitude, "longitude");
+    // checkParameter(latitude, "latitude");
+    // checkParameter(vs30, "vs30");
+    // }
   }
 
-  private static void checkParameter(Object param, String id) {
-    checkNotNull(param, "Missing parameter: %s", id);
-    // TODO check range here
-  }
+  // private static void checkParameter(Object param, String id) {
+  // checkNotNull(param, "Missing parameter: %s", id);
+  // // TODO check range here
+  // }
 
   /* Service request and model metadata */
   static class RequestMetadata {
@@ -208,11 +192,15 @@ public final class HazardService {
     final double longitude;
     final double latitude;
     final double vs30;
+    final boolean truncate;
+    final boolean maxdir;
 
     RequestData(QueryParameters query) {
-      this.longitude = query.longitude.orElseThrow();
-      this.latitude = query.latitude.orElseThrow();
-      this.vs30 = query.vs30.orElseThrow();
+      this.longitude = query.longitude;
+      this.latitude = query.latitude;
+      this.vs30 = query.vs30;
+      this.truncate = query.truncate;
+      this.maxdir = query.maxdir;
     }
   }
 
@@ -271,9 +259,7 @@ public final class HazardService {
 
     Curve(String component, XySequence values) {
       this.component = component;
-      this.values = XySequence.create(
-          values.xValues().map(Math::exp).toArray(),
-          values.yValues().toArray());
+      this.values = values;
     }
   }
 
@@ -341,12 +327,16 @@ public final class HazardService {
         var curves = new ArrayList<Curve>();
 
         // total curve
-        curves.add(new Curve(TOTAL_KEY, totalMap.get(imt)));
+        curves.add(new Curve(
+            TOTAL_KEY,
+            updateCurve(request, totalMap.get(imt), imt)));
 
         // component curves
         var typeMap = componentMaps.get(imt);
         for (SourceType type : typeMap.keySet()) {
-          curves.add(new Curve(type.toString(), typeMap.get(type)));
+          curves.add(new Curve(
+              type.toString(),
+              updateCurve(request, typeMap.get(type), imt)));
         }
 
         hazards.add(new HazardResponse(imt, List.copyOf(curves)));
@@ -359,6 +349,44 @@ public final class HazardService {
     }
   }
 
+  private static final double TRUNCATION_LIMIT = 1e-4;
+
+  /* Convert to linear and possibly truncate and scale to max-direction. */
+  private static XySequence updateCurve(
+      RequestData request,
+      XySequence curve,
+      Imt imt) {
+
+    /*
+     * If entire curve is <1e-4, this method will return a curve consisting of
+     * just the first point in the supplied curve.
+     *
+     * TODO We probably want to move the TRUNCATION_LIMIT out to a config.
+     */
+
+    double[] yValues = curve.yValues().toArray();
+    int limit = request.truncate ? truncationLimit(yValues) : yValues.length;
+    yValues = Arrays.copyOf(yValues, limit);
+
+    double scale = request.maxdir ? MaxDirection.FACTORS.get(imt) : 1.0;
+    double[] xValues = curve.xValues()
+        .limit(yValues.length)
+        .map(Math::exp)
+        .map(x -> x * scale)
+        .toArray();
+
+    return XySequence.create(xValues, yValues);
+  }
+
+  private static int truncationLimit(double[] yValues) {
+    int limit = 1;
+    double y = yValues[0];
+    while (y >= TRUNCATION_LIMIT && limit < yValues.length) {
+      y = yValues[limit++];
+    }
+    return limit;
+  }
+
   @Deprecated
   public static class Query extends ServiceQueryData {
     Integer vs30;