diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/source/TestSitesService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/FeatureCollectionService.java
similarity index 66%
rename from src/main/java/gov/usgs/earthquake/nshmp/www/source/TestSitesService.java
rename to src/main/java/gov/usgs/earthquake/nshmp/www/source/FeatureCollectionService.java
index 643a2b89a0b59c9e2da740d51f6b51df4fff742a..eb7bf05e76eb6edf929f1ed00cfe26dcada4cf2b 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/source/TestSitesService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/FeatureCollectionService.java
@@ -1,8 +1,5 @@
 package gov.usgs.earthquake.nshmp.www.source;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import com.google.gson.JsonElement;
 
 import gov.usgs.earthquake.nshmp.geo.json.FeatureCollection;
@@ -15,28 +12,29 @@ import io.micronaut.http.HttpRequest;
 import jakarta.inject.Singleton;
 
 /**
- * Test sites handler for {@link TestSitesController}.
+ * Handle feature collection services.
  *
  * @author U.S. Geological Survey
  */
 @Singleton
-public class TestSitesService {
-  static final Logger LOG = LoggerFactory.getLogger(TestSitesController.class);
-  static final String NAME = "Test Sites";
+public class FeatureCollectionService {
 
-  static String handleSites(HttpRequest<?> http, Boolean raw) {
+  static String handleFeatureCollection(
+      HttpRequest<?> http,
+      String name,
+      FeatureCollection fc,
+      Boolean raw) {
     RequestData requestData = new RequestData(raw);
-    FeatureCollection sites = ServletUtil.model().sites().orElseThrow();
 
     if (requestData.raw) {
-      return sites.toJson();
+      return fc.toJson();
     } else {
       var response = ResponseBody.<RequestData, JsonElement> success()
-          .name(NAME)
+          .name(name)
           .url(http.getUri().toString())
           .metadata(new ResponseMetadata(HazVersion.appVersions()))
           .request(requestData)
-          .response(sites.toJsonTree())
+          .response(fc.toJsonTree())
           .build();
 
       return ServletUtil.GSON2.toJson(response);
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/source/MapController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/MapController.java
new file mode 100644
index 0000000000000000000000000000000000000000..dffe697935206db22071c661dbfaeb25e47d3657
--- /dev/null
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/MapController.java
@@ -0,0 +1,94 @@
+package gov.usgs.earthquake.nshmp.www.source;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import gov.usgs.earthquake.nshmp.geo.json.FeatureCollection;
+import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
+import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ServletUtil;
+import gov.usgs.earthquake.nshmp.www.source.FeatureCollectionService.RequestData;
+
+import io.micronaut.core.annotation.Nullable;
+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;
+import io.micronaut.http.annotation.QueryValue;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Inject;
+
+/**
+ * Micronaut web service controller for the GeoJSON map boundary associated with
+ * the current installed model.
+ *
+ * <p>See src/main/resources/application.yml nshmp-haz.model-path for installed
+ * model.
+ *
+ * <p>To run the Micronaut jar file with a model: java -jar
+ * path/to/nshmp-haz.jar --model=<path/to/model>
+ *
+ * @author U.S. Geological Survey
+ */
+@Tag(
+    name = MapController.NAME,
+    description = "NSHM map boundary")
+@Controller("/map")
+public class MapController {
+  static final Logger LOG = LoggerFactory.getLogger(MapController.class);
+  static final String NAME = "Map Boundary";
+
+  @Inject
+  private NshmpMicronautServlet servlet;
+
+  @Operation(
+      summary = "Get the GeoJSON map boundary",
+      description = "Returns the feature collection of NSHM map boundary",
+      operationId = "map")
+  @ApiResponse(
+      description = "NSHM map boundary",
+      responseCode = "200",
+      content = @Content(
+          schema = @Schema(implementation = Response.class)))
+  @Get(uri = "{?raw}", produces = MediaType.APPLICATION_JSON)
+  public HttpResponse<String> doGet(
+      HttpRequest<?> http,
+      @QueryValue(defaultValue = "false") @Nullable Boolean raw) {
+    try {
+      FeatureCollection map = ServletUtil.model().map().orElseThrow();
+      return HttpResponse
+          .ok(FeatureCollectionService.handleFeatureCollection(http, NAME, map, raw));
+    } catch (Exception e) {
+      return ServletUtil.error(
+          LOG,
+          e,
+          NAME,
+          http.getUri().toString());
+    }
+  }
+
+  @Operation(
+      summary = "Get the GeoJSON map boundary",
+      description = "Returns the feature collection of NSHM map boundary",
+      operationId = "map-slash")
+  @ApiResponse(
+      description = "NSHM map boundary",
+      responseCode = "200",
+      content = @Content(
+          schema = @Schema(implementation = Response.class)))
+  @Get(uri = "/{raw}", produces = MediaType.APPLICATION_JSON)
+  public HttpResponse<String> doGetSlash(
+      HttpRequest<?> http,
+      @PathVariable(defaultValue = "false") @Nullable Boolean raw) {
+    return doGet(http, raw);
+  }
+
+  // Swagger schema
+  private static class Response extends ResponseBody<RequestData, FeatureCollection> {};
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceService.java
index 1be96725a61965e3d399c1ce46b36d20654f15f1..6cedce847c694e6243773f74bde87e91ba7be2f5 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceService.java
@@ -10,7 +10,6 @@ import java.util.stream.DoubleStream;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import gov.usgs.earthquake.nshmp.geo.json.FeatureCollection;
 import gov.usgs.earthquake.nshmp.gmm.Gmm;
 import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
@@ -74,7 +73,6 @@ public class SourceService {
     final Map<NehrpSiteClass, Double> siteClasses;
     final List<Parameter> imts;
     final List<Double> bounds;
-    final FeatureCollection map;
 
     public SourceModel(HazardModel model) {
       name = model.name();
@@ -88,7 +86,6 @@ public class SourceService {
           .map(imt -> new Parameter(ServletUtil.imtShortLabel(imt), imt.name()))
           .collect(toList());
       bounds = DoubleStream.of(model.bounds().toArray()).boxed().collect(toList());
-      map = model.map().orElseThrow();
     }
 
     public String getName() {
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/source/TestSitesController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/TestSitesController.java
index 120cc13fb4919af2103585e6b8313ecba0b3390d..45fa30f2fff3a988bf69c2a7f4a33afd00c44434 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/source/TestSitesController.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/TestSitesController.java
@@ -1,10 +1,13 @@
 package gov.usgs.earthquake.nshmp.www.source;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import gov.usgs.earthquake.nshmp.geo.json.FeatureCollection;
 import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
-import gov.usgs.earthquake.nshmp.www.source.TestSitesService.RequestData;
+import gov.usgs.earthquake.nshmp.www.source.FeatureCollectionService.RequestData;
 
 import io.micronaut.core.annotation.Nullable;
 import io.micronaut.http.HttpRequest;
@@ -34,10 +37,12 @@ import jakarta.inject.Inject;
  * @author U.S. Geological Survey
  */
 @Tag(
-    name = TestSitesService.NAME,
+    name = TestSitesController.NAME,
     description = "NSHM test sites")
 @Controller("/sites")
 public class TestSitesController {
+  static final Logger LOG = LoggerFactory.getLogger(TestSitesController.class);
+  static final String NAME = "Test Sites";
 
   @Inject
   private NshmpMicronautServlet servlet;
@@ -56,12 +61,14 @@ public class TestSitesController {
       HttpRequest<?> http,
       @QueryValue(defaultValue = "false") @Nullable Boolean raw) {
     try {
-      return HttpResponse.ok(TestSitesService.handleSites(http, raw));
+      FeatureCollection sites = ServletUtil.model().sites().orElseThrow();
+      return HttpResponse
+          .ok(FeatureCollectionService.handleFeatureCollection(http, NAME, sites, raw));
     } catch (Exception e) {
       return ServletUtil.error(
-          TestSitesService.LOG,
+          LOG,
           e,
-          TestSitesService.NAME,
+          NAME,
           http.getUri().toString());
     }
   }