diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7438e952026b0e53bde8ace15a9707d786403747..d3791ed02eb6d4c8cddd67e639e5cb55298d15e6 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,8 +1,8 @@
 variables:
   IMAGE_NAME: ${CODE_REGISTRY_IMAGE}/${CI_PROJECT_NAME}:${ENVIRONMENT}-${CI_COMMIT_SHORT_SHA}
   JACOCO_HTML_DIR: ${REPORTS_DIR}/jacoco/test/html
-  JUNIT_FILES: build/test-results/test/TEST-*.xml
-  REPORTS_DIR: build/reports
+  JUNIT_FILES: src/lib/build/test-results/test/TEST-*.xml
+  REPORTS_DIR: src/lib/build/reports
 
 stages:
   - build
@@ -111,6 +111,13 @@ workflow:
       ]]; then
         docker tag "${latest_image_name}" "usgs/${CI_PROJECT_NAME}:${ENVIRONMENT}-latest";
         docker push "usgs/${CI_PROJECT_NAME}:${ENVIRONMENT}-latest";
+
+        if [[ ${CI_COMMIT_REF_SLUG} == "${CI_DEFAULT_BRANCH}" ]]; then
+          docker tag \
+              "usgs/${CI_PROJECT_NAME}:${ENVIRONMENT}-latest" \
+              "usgs/${CI_PROJECT_NAME}:latest";
+          docker push "usgs/${CI_PROJECT_NAME}:latest";
+        fi
       fi
     - |
       printf "
@@ -152,7 +159,7 @@ Build Image:
     DOCKER_BUILD_ARGS: |
       BUILD_IMAGE=${DEVOPS_REGISTRY}usgs/amazoncorretto:11
       FROM_IMAGE=${DEVOPS_REGISTRY}usgs/amazoncorretto:11
-      ci_job_token=${CI_JOB_TOKEN}
+      CI_JOB_TOKEN=${CI_JOB_TOKEN}
     UPSTREAM_PATH: ghsc/nshmp/nshmp-ws-static
 
 Build Project:
diff --git a/Dockerfile b/Dockerfile
index 6e0c8c43211e4f533bbc4a4f5d73f0acad640ea7..c363c429a6856e6d6ff3c2239f0c9686950b6ad4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,12 +10,9 @@ ARG FROM_IMAGE=usgs/amazoncorretto:11
 
 FROM ${BUILD_IMAGE} as builder
 
-ARG gitlab_token=null
-ARG ci_job_token=null
-
 # TODO: Token needed until nshmp-lib is public
-ENV GITLAB_TOKEN ${gitlab_token}
-ENV CI_JOB_TOKEN ${ci_job_token}
+ARG GITLAB_TOKEN=null
+ARG CI_JOB_TOKEN=null
 
 WORKDIR /app
 
@@ -28,20 +25,27 @@ RUN ./gradlew assemble
 ####
 FROM ${FROM_IMAGE}
 
-# Path to the NetCDF file to use
-ENV NETCDF_FILE hazard-example.nc
+# Which service to run: hazard or aashto
+ENV SERVICE="hazard"
+# Web service context path
 ENV CONTEXT_PATH "/"
 ENV JAVA_OPTS=""
 
+# Path to the NetCDF file to use
+ENV NETCDF_FILE ${SERVICE}-example.nc
+
 WORKDIR /app
 
-COPY --from=builder /app/build/libs/nshmp-ws-static-all.jar nshmp-ws-static.jar
-COPY --from=builder /app/src/main/resources/hazard-example.nc .
+COPY --from=builder /app/src/aashto/build/libs/aashto-all.jar aashto.jar
+COPY --from=builder /app/src/aashto/src/main/resources/aashto-example.nc .
+
+COPY --from=builder /app/src/hazard/build/libs/hazard-all.jar hazard.jar
+COPY --from=builder /app/src/hazard/src/main/resources/hazard-example.nc .
 
 ENTRYPOINT /usr/bin/java \
     ${JAVA_OPTS} \
     -jar \
-    nshmp-ws-static.jar \
+    "${SERVICE}".jar \
     "-Dmicronaut.server.context-path=${CONTEXT_PATH}" \
     -netcdf=${NETCDF_FILE}
 
diff --git a/README.md b/README.md
index 7e07378edf20cd10b1886b087d6c47c08f22c70b..87adcf8a2ce6383fe6336f0af7461a15274f3d97 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,81 @@
 # nshmp-ws-static
 
-Static hazard web service.
+Serve Hazard and AASHTO NetCDF files from
+[nshmp-netcdf-conversion](https://code.usgs.gov/ghsc/nshmp/nshmp-netcdf-conversion).
+
+## Sub-Projects
+
+The repository is split into gradle sub-projects:
+
+- [aashto](./src/aashto/): Web services for AASHTO static NetCDF files
+- [hazard](./src/hazard/): Web services for hazard static NetCDF files
+- [lib](./src/lib/): Common library for web services
+
+### AASHTO
+
+Miconaut web services for AASHTO ground motions.
+
+To run AASHTO web services, from root project directory run:
+
+```bash
+./gradle runAashto
+```
+
+### Hazard
+
+Micronaut web services for hazard curves.
+
+To run hazard web services, from root project directory run:
+
+```bash
+./gradlew runHazard
+```
+
+## Docker
+
+### Public Image
+
+The web services can be pulled and ran from Docker hub given one of the four
+available tags:
+
+- usgs/nshmp-ws-static:development-latest - Is based on developer forks and is considered unstable
+- usgs/nshmp-ws-static:staging-latest -  Is based on the main branch and is the latest
+- usgs/nshmp-ws-static:production-latest - Is based on the production branch and is stable
+- usgs/nshmp-ws-static:latest - Is based on the main branch
+
+#### Pull Image
+
+```bash
+docker pull usgs/nshmp-ws-static:production-latest
+```
+
+#### Run Service
+
+```bash
+docker run -e SERVICE={service} -p 8080:8080 usgs/nshmp-ws-static:production-latest
+```
+
+> Replace `{service}` with the service to run, either `hazard` or `aashto`
+
+##### Run AASHTO Service
+
+```bash
+docker run -e SERVICE=hazard -p 8080:8080 usgs/nshmp-ws-static:production-latest
+```
+
+##### Run Hazard Service
+
+```bash
+docker run -e SERVICE=aashto -p 8080:8080 usgs/nshmp-ws-static:production-latest
+```
+
+### Local Build
+
+To build the Docker image locally run:
+
+```bash
+docker build --build-arg GITLAB_TOKEN="{gitlab_token}" -t nshmp-ws-static .
+```
+
+> Replace `{gitlab_token}` with the GitLab API
+> [access token](https://code.usgs.gov/-/profile/personal_access_tokens)
diff --git a/build.gradle b/build.gradle
index a36b11a5d79fde0503e70fc7cbb65dc5066efd8c..13c5a81698e1bd06cc7a401fa3222a1d73855c39 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,4 @@
 plugins {
-  id "application"
   id "com.diffplug.spotless" version "${spotlessVersion}"
   id "com.github.johnrengelman.shadow" version "${shadowVersion}"
   id "com.github.node-gradle.node" version "${nodePluginVersion}"
@@ -8,61 +7,73 @@ plugins {
   id "eclipse-wtp"
   id "jacoco"
   id "java"
-  id "maven-publish"
 }
 
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
-mainClassName = "gov.usgs.earthquake.nshmp.netcdf.www.Application"
-sourceCompatibility = JavaVersion.VERSION_11
-
-apply from: "${projectDir}/gradle/dependencies.gradle"
-apply from: "${projectDir}/gradle/git-hooks.gradle"
-apply from: "${projectDir}/gradle/node.gradle"
-apply from: "${projectDir}/gradle/repositories.gradle"
-apply from: "${projectDir}/gradle/spotbugs.gradle"
-apply from: "${projectDir}/gradle/spotless.gradle"
-
-test {
-  useJUnitPlatform()
+task runAashto(type: JavaExec) {
+  dependsOn(":src:aashto:run")
 }
 
-jacoco {
-  toolVersion = "0.8.4"
+task runHazard(type: JavaExec) {
+  dependsOn(":src:hazard:run")
 }
 
-jacocoTestReport {
-  reports {
-    xml.enabled true
-    html.enabled true
+apply from: "${rootDir}/gradle/git-hooks.gradle"
+apply from: "${rootDir}/gradle/node.gradle"
+
+subprojects {
+  apply plugin: "application"
+  apply plugin: "com.diffplug.spotless"
+  apply plugin: "com.github.johnrengelman.shadow"
+  apply plugin: "com.github.node-gradle.node"
+  apply plugin: "com.github.spotbugs"
+  apply plugin: "com.star-zero.gradle.githook"
+  apply plugin: "com.github.johnrengelman.shadow"
+  apply plugin: "eclipse-wtp"
+  apply plugin: "jacoco"
+  apply plugin: "java"
+
+  application {
+    mainClass = "gov.usgs.earthquake.nshmp.netcdf.www.Application"
   }
-}
-check.dependsOn jacocoTestReport
 
-shadowJar {
-  mergeServiceFiles()
-}
+  compileJava.options.encoding = "UTF-8"
+  compileTestJava.options.encoding = "UTF-8"
+  sourceCompatibility = JavaVersion.VERSION_11
+  targetCompatibility = JavaVersion.VERSION_11
 
-tasks.withType(JavaCompile) {
-  options.encoding = "UTF-8"
-  options.compilerArgs.add("-parameters")
-}
+  apply from: "${rootDir}/gradle/dependencies.gradle"
+  apply from: "${rootDir}/gradle/git-hooks.gradle"
+  apply from: "${rootDir}/gradle/repositories.gradle"
+  apply from: "${rootDir}/gradle/spotbugs.gradle"
+  apply from: "${rootDir}/gradle/spotless.gradle"
 
-tasks.withType(JavaExec) {
-  jvmArgs('-noverify', '-XX:TieredStopAtLevel=1', '-Dcom.sun.management.jmxremote')
-}
+  test {
+    useJUnitPlatform()
+  }
 
-task libsClean(type: Delete) {
-  delete "libs"
-}
-clean.dependsOn libsClean
+  jacoco {
+    toolVersion = "0.8.4"
+  }
+
+  jacocoTestReport {
+    reports {
+      xml.enabled true
+      html.enabled true
+    }
+  }
+  check.dependsOn jacocoTestReport
+
+  shadowJar {
+    mergeServiceFiles()
+  }
 
-/* SpotBugs */
-tasks.withType(com.github.spotbugs.snom.SpotBugsTask) {
-  reports {
-    html {
-      enabled true
-      stylesheet = 'fancy-hist.xsl'
+  /* SpotBugs */
+  tasks.withType(com.github.spotbugs.snom.SpotBugsTask) {
+    reports {
+      html {
+        enabled true
+        stylesheet = 'fancy-hist.xsl'
+      }
     }
   }
 }
diff --git a/gradle.properties b/gradle.properties
index 4c9854e9cc11223fbfb14bc8ffefa5c59798535d..dadbc38cf62b155aa9e6bfffd68a9d2856b23417 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -8,7 +8,7 @@ nodePluginVersion = 3.0.1
 nodeVersion = 16.3.0
 nshmpLibVersion = 0.8.2
 nshmpWsUtilsVersion = 0.1.3
-shadowVersion = 7.1.2
+shadowVersion = 7.1.1
 slfVersion = 1.7.30
 spotbugsVersion = 4.7.0
 spotlessVersion = 6.0.4
diff --git a/gradle/repositories.gradle b/gradle/repositories.gradle
index 11104ea1c6947e89e8906f98b427188bbc698238..09519817c26f8f1bd4eb5bd436c8862ca8659094 100644
--- a/gradle/repositories.gradle
+++ b/gradle/repositories.gradle
@@ -1,29 +1,4 @@
 
-publishing {
-  publications {
-    library(MavenPublication) {
-      groupId = "ghsc"
-      version = project.getProperty("version")
-      version = version == "unspecified" ? "latest" : version
-      from components.java
-    }
-  }
-
-  repositories {
-    maven {
-      url "https://code.usgs.gov/api/v4/projects/3339/packages/maven"
-      name = "GitLab"
-      credentials(HttpHeaderCredentials) {
-        name = 'Job-Token'
-        value = System.getenv("CI_JOB_TOKEN")
-      }
-      authentication {
-        header(HttpHeaderAuthentication)
-      }
-    }
-  }
-}
-
 repositories {
   mavenCentral()
 
diff --git a/gradle/spotbugs.gradle b/gradle/spotbugs.gradle
index 51f1a63a2462892bd53bd2c7ebb05a6a6636653e..32ae4ab0bcb0b1e12cc7ebe657c34b7cb7023585 100644
--- a/gradle/spotbugs.gradle
+++ b/gradle/spotbugs.gradle
@@ -6,6 +6,6 @@ apply plugin: "com.github.spotbugs"
  * See https://spotbugs.readthedocs.io
  */
 spotbugs {
-  excludeFilter = file("${projectDir}/gradle/spotbugs-filter.xml")
+  excludeFilter = file("${rootDir}/gradle/spotbugs-filter.xml")
   effort = "max"
 }
diff --git a/gradle/spotless.gradle b/gradle/spotless.gradle
index 2d827774b345380999def6ebdea39fc7ad5f838e..2025f24761e9fb70d1a8d20216d5ac1066b445d8 100644
--- a/gradle/spotless.gradle
+++ b/gradle/spotless.gradle
@@ -25,8 +25,8 @@ spotless {
       }
     }
 
-    importOrderFile "${projectDir}/src/main/resources/eclipse.importorder"
-    eclipse().configFile "${projectDir}/src/main/resources/nshmp.eclipse-format.xml"
+    importOrderFile "${rootDir}/src/lib/src/main/resources/eclipse.importorder"
+    eclipse().configFile "${rootDir}/src/lib/src/main/resources/nshmp.eclipse-format.xml"
 
     trimTrailingWhitespace()
     indentWithSpaces(2)
@@ -59,7 +59,7 @@ spotless {
     target fileTree(".") {
       include "**/*.xml"
       exclude "**/build", ".settings", ".classpath", ".project",
-          "tmp/**", ".gradle/**", "libs/**", "node_modules"
+          "tmp", ".gradle", "libs", "node_modules", "**/.gradle", "bin", "**/bin"
     }
     eclipseWtp("xml")
     trimTrailingWhitespace()
diff --git a/settings.gradle b/settings.gradle
index bf70eec81ed3bebe3c40593f3557d30dbfcd7a12..e501c4c51312f635f6ec9c4dfdb993465fae552a 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1,2 @@
 rootProject.name = "nshmp-ws-static"
+include("src:aashto", "src:hazard", "src:lib")
diff --git a/src/aashto/build.gradle b/src/aashto/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..747bdaf3abdb2fccdeb72b6c9154a6b395fdb254
--- /dev/null
+++ b/src/aashto/build.gradle
@@ -0,0 +1,4 @@
+
+dependencies {
+  implementation project(":src:lib")
+}
diff --git a/openapi.properties b/src/aashto/openapi.properties
similarity index 100%
rename from openapi.properties
rename to src/aashto/openapi.properties
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfGroundMotions.java b/src/aashto/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfGroundMotions.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfGroundMotions.java
rename to src/aashto/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfGroundMotions.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderGroundMotions.java b/src/aashto/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderGroundMotions.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderGroundMotions.java
rename to src/aashto/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderGroundMotions.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/ReaderGroundMotions.java b/src/aashto/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/ReaderGroundMotions.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/ReaderGroundMotions.java
rename to src/aashto/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/ReaderGroundMotions.java
diff --git a/src/aashto/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/Application.java b/src/aashto/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/Application.java
new file mode 100644
index 0000000000000000000000000000000000000000..85cdcf7d29737c2ec14c04923e168bad6666c0ab
--- /dev/null
+++ b/src/aashto/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/Application.java
@@ -0,0 +1,21 @@
+package gov.usgs.earthquake.nshmp.netcdf.www;
+
+import io.micronaut.runtime.Micronaut;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.info.Info;
+
+@OpenAPIDefinition(
+    info = @Info(
+        title = "AASHTO Ground Motion Data",
+        description = "### Get static curves\n" +
+            "See the service usage for supported longitudes, " +
+            "latitudes, and site classes"))
+public class Application {
+
+  public static void main(String[] args) {
+    Micronaut.build(args)
+        .mainClass(Application.class)
+        .start();
+  }
+
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfController.java b/src/aashto/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfController.java
similarity index 81%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfController.java
rename to src/aashto/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfController.java
index 81ac818c3b102ab400864839c54d5a8454d4e19b..0d3fa24ef6e36f13ecbead81df6d139406fd0d4b 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfController.java
+++ b/src/aashto/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfController.java
@@ -3,9 +3,7 @@ package gov.usgs.earthquake.nshmp.netcdf.www;
 import java.nio.file.Path;
 
 import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
-import gov.usgs.earthquake.nshmp.netcdf.NetcdfDataType;
 import gov.usgs.earthquake.nshmp.netcdf.NetcdfGroundMotions;
-import gov.usgs.earthquake.nshmp.netcdf.NetcdfHazardCurves;
 import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
 
 import io.micronaut.context.annotation.Value;
@@ -34,8 +32,8 @@ import jakarta.inject.Inject;
  *
  * @author U.S. Geological Survey
  */
-@Tag(name = "Static Data")
-@Controller("/curves")
+@Tag(name = "Static Ground Motion Data")
+@Controller("/ground-motions")
 public class NetcdfController {
 
   @Inject
@@ -44,27 +42,15 @@ public class NetcdfController {
   @Value("${nshmp-ws-static.netcdf-file}")
   Path netcdfPath;
 
-  NetcdfService service;
+  NetcdfServiceGroundMotions service;
 
   /**
    * Read in data type and return the appropriate service to use.
    */
   @EventListener
   void startup(StartupEvent event) {
-    var dataType = NetcdfDataType.getDataType(netcdfPath);
-
-    switch (dataType) {
-      case GROUND_MOTIONS:
-        var netcdfGroundMotions = new NetcdfGroundMotions(netcdfPath);
-        service = new NetcdfServiceGroundMotions(netcdfGroundMotions);
-        break;
-      case HAZARD_CURVES:
-        var netcdfHazard = new NetcdfHazardCurves(netcdfPath);
-        service = new NetcdfServiceHazardCurves(netcdfHazard);
-        break;
-      default:
-        throw new RuntimeException("Data type [" + dataType + "] not supported");
-    }
+    var netcdfHazard = new NetcdfGroundMotions(netcdfPath);
+    service = new NetcdfServiceGroundMotions(netcdfHazard);
   }
 
   /**
@@ -146,22 +132,4 @@ public class NetcdfController {
       @Schema(required = true) @PathVariable @Nullable Double latitude) {
     return doGet(request, longitude, latitude, null);
   }
-
-  static class Query {
-    final Double longitude;
-    final Double latitude;
-    final NehrpSiteClass siteClass;
-
-    Query(Double longitude, Double latitude, NehrpSiteClass siteClass) {
-      this.longitude = longitude;
-      this.latitude = latitude;
-      this.siteClass = siteClass;
-    }
-  }
-
-  static enum Service {
-    BOUNDING,
-    CURVES,
-    CURVES_BY_SITE_CLASS,
-  }
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceGroundMotions.java b/src/aashto/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceGroundMotions.java
similarity index 95%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceGroundMotions.java
rename to src/aashto/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceGroundMotions.java
index 4624d09c6d4c9aa816e29c6462d119c7d269f31f..1e045cc5d527ba560e5724619f076bcfec3d382d 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceGroundMotions.java
+++ b/src/aashto/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceGroundMotions.java
@@ -11,9 +11,8 @@ import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
 import gov.usgs.earthquake.nshmp.netcdf.NetcdfGroundMotions;
 import gov.usgs.earthquake.nshmp.netcdf.data.StaticData;
 import gov.usgs.earthquake.nshmp.netcdf.reader.BoundingReaderGroundMotions;
-import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfController.Query;
-import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfController.Service;
 import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfWsUtils.Key;
+import gov.usgs.earthquake.nshmp.netcdf.www.Query.Service;
 import gov.usgs.earthquake.nshmp.www.Response;
 import gov.usgs.earthquake.nshmp.www.WsUtils;
 import gov.usgs.earthquake.nshmp.www.meta.Status;
@@ -34,7 +33,7 @@ public class NetcdfServiceGroundMotions extends NetcdfService {
   static final String X_LABEL = "Period (s)";
   static final String Y_LABEL = "Median Ground Motion (g)";
 
-  protected NetcdfServiceGroundMotions(NetcdfGroundMotions netcdf) {
+  public NetcdfServiceGroundMotions(NetcdfGroundMotions netcdf) {
     super(netcdf);
   }
 
diff --git a/src/main/resources/rtsa-example.nc b/src/aashto/src/main/resources/aashto-example.nc
similarity index 100%
rename from src/main/resources/rtsa-example.nc
rename to src/aashto/src/main/resources/aashto-example.nc
diff --git a/src/aashto/src/main/resources/application.yml b/src/aashto/src/main/resources/application.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a6bdb5f70547e8371b49cd2d8e491ed617f5f259
--- /dev/null
+++ b/src/aashto/src/main/resources/application.yml
@@ -0,0 +1,14 @@
+micronaut:
+  io:
+    watch:
+      paths: src
+      restart: true
+  router:
+    static-resources:
+      swagger:
+        enabled: true
+        paths: classpath:swagger
+        mapping: /**
+
+nshmp-ws-static:
+  netcdf-file: ${netcdf:src/main/resources/aashto-example.nc}
diff --git a/src/main/resources/logback.xml b/src/aashto/src/main/resources/logback.xml
similarity index 100%
rename from src/main/resources/logback.xml
rename to src/aashto/src/main/resources/logback.xml
diff --git a/src/hazard/build.gradle b/src/hazard/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..747bdaf3abdb2fccdeb72b6c9154a6b395fdb254
--- /dev/null
+++ b/src/hazard/build.gradle
@@ -0,0 +1,4 @@
+
+dependencies {
+  implementation project(":src:lib")
+}
diff --git a/src/hazard/openapi.properties b/src/hazard/openapi.properties
new file mode 100644
index 0000000000000000000000000000000000000000..75e362540a617f22719aa540e333d8a469f33b0b
--- /dev/null
+++ b/src/hazard/openapi.properties
@@ -0,0 +1 @@
+micronaut.openapi.target.file = build/classes/java/main/META-INF/swagger/nshmp-ws-static.yml
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfHazardCurves.java b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfHazardCurves.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfHazardCurves.java
rename to src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfHazardCurves.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfDataHazardCurves.java b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfDataHazardCurves.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfDataHazardCurves.java
rename to src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfDataHazardCurves.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/StaticDataHazardCurves.java b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/StaticDataHazardCurves.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/StaticDataHazardCurves.java
rename to src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/StaticDataHazardCurves.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderHazardCurves.java b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderHazardCurves.java
similarity index 96%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderHazardCurves.java
rename to src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderHazardCurves.java
index 596769d81ff0b96e95879f454ab6b1491cc35e2f..0d53e4e769fbc0beaf259848d23d82ddee690a11 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderHazardCurves.java
+++ b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderHazardCurves.java
@@ -104,9 +104,9 @@ public class BoundingReaderHazardCurves extends BoundingReader<StaticDataHazardC
       StaticData<StaticDataHazardCurves> d1,
       StaticData<StaticDataHazardCurves> d2,
       double frac) {
-    NetcdfUtils.checkBoundingHazard(d1, d2);
+    HazardNetcdfUtils.checkBoundingHazard(d1, d2);
     return frac == 0.0 ? d1
-        : frac == 1.0 ? d2 : NetcdfUtils.linearInterpolateHazardCurves(d1, d2, frac);
+        : frac == 1.0 ? d2 : HazardNetcdfUtils.linearInterpolateHazardCurves(d1, d2, frac);
   }
 
   @Override
@@ -180,7 +180,7 @@ public class BoundingReaderHazardCurves extends BoundingReader<StaticDataHazardC
         .build();
     var boundingData = builder.build();
 
-    NetcdfUtils.checkBoundingHazards(boundingData, boundingLocations.get(0).location);
+    HazardNetcdfUtils.checkBoundingHazards(boundingData, boundingLocations.get(0).location);
     return boundingData;
   }
 }
diff --git a/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/HazardNetcdfUtils.java b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/HazardNetcdfUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..610c42358865834593727061666975be45ae8e3c
--- /dev/null
+++ b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/HazardNetcdfUtils.java
@@ -0,0 +1,95 @@
+package gov.usgs.earthquake.nshmp.netcdf.reader;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static gov.usgs.earthquake.nshmp.netcdf.reader.NetcdfUtils.checkXySequence;
+
+import gov.usgs.earthquake.nshmp.data.XySequence;
+import gov.usgs.earthquake.nshmp.geo.Location;
+import gov.usgs.earthquake.nshmp.netcdf.data.BoundingData;
+import gov.usgs.earthquake.nshmp.netcdf.data.StaticData;
+import gov.usgs.earthquake.nshmp.netcdf.data.StaticDataHazardCurves;
+
+public class HazardNetcdfUtils {
+  /**
+   * Check whether bounding hazards contain the same: Site classes, IMTs per
+   * each site class, and ground motions per each IMT
+   *
+   * @param a Bounding hazard map A
+   * @param b Bounding hazard map B
+   */
+  static void checkBoundingHazard(
+      StaticData<StaticDataHazardCurves> a,
+      StaticData<StaticDataHazardCurves> b) {
+    checkState(a.size() == b.size(), "Maps are not the same size");
+    checkState(a.keySet().containsAll(b.keySet()), "Site classes do not match");
+    a.keySet().forEach(key -> checkHazards(a.get(key), b.get(key)));
+  }
+
+  /**
+   * Checks bounding hazard maps contain the same: Site classes, IMTs per each
+   * site class, and ground motions per each IMT
+   *
+   * @param boundingData The bounding hazards
+   */
+  static void checkBoundingHazards(
+      BoundingData<StaticDataHazardCurves> boundingData,
+      Location location) {
+    checkArgument(boundingData.containsKey(location), "Location not in bounding hazards");
+    boundingData.keySet().stream()
+        .filter(loc -> loc.equals(location))
+        .forEach(key -> {
+          checkBoundingHazard(boundingData.get(location), boundingData.get(key));
+        });
+  }
+
+  /**
+   * Check whether hazards contain the same: IMTs and ground motions per each
+   * IMT
+   *
+   * @param a Hazard A
+   * @param b Hazard B
+   */
+  static void checkHazards(
+      StaticDataHazardCurves a,
+      StaticDataHazardCurves b) {
+    checkState(a.size() == b.size(), "Maps are not the same size");
+    checkState(a.keySet().containsAll(b.keySet()), "IMTs do not match");
+    a.keySet().forEach(key -> checkXySequence(a.get(key), b.get(key)));
+  }
+
+  /*
+   * Linear interpolation of data values to a target point
+   */
+  static StaticData<StaticDataHazardCurves> linearInterpolateHazardCurves(
+      StaticData<StaticDataHazardCurves> v1,
+      StaticData<StaticDataHazardCurves> v2,
+      double frac) {
+    checkBoundingHazard(v1, v2);
+
+    var targetMap = StaticData.<StaticDataHazardCurves> builder();
+
+    v1.keySet().forEach(siteClass -> {
+      var imtHazards = StaticDataHazardCurves.builder();
+      var v1StaticHazards = v1.get(siteClass);
+      var v2StaticHazards = v2.get(siteClass);
+
+      v1StaticHazards.keySet().forEach(imt -> {
+        var v1Haz = v1StaticHazards.get(imt).yValues().toArray();
+        var v2Haz = v2StaticHazards.get(imt).yValues().toArray();
+        var target = new double[v1Haz.length];
+
+        for (int i = 0; i < v1Haz.length; i++) {
+          target[i] = v1Haz[i] * (1 - frac) + v2Haz[i] * frac;
+        }
+
+        var xValues = v1StaticHazards.get(imt).xValues().toArray();
+        imtHazards.put(imt, XySequence.create(xValues, target));
+      });
+
+      targetMap.put(siteClass, imtHazards.build());
+    });
+
+    return targetMap.build();
+  }
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/ReaderHazardCurves.java b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/ReaderHazardCurves.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/ReaderHazardCurves.java
rename to src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/ReaderHazardCurves.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/Application.java b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/Application.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/Application.java
rename to src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/Application.java
diff --git a/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfController.java b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfController.java
new file mode 100644
index 0000000000000000000000000000000000000000..9f14c71af1f2af7d901cd429cf2775175c397bcd
--- /dev/null
+++ b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfController.java
@@ -0,0 +1,135 @@
+package gov.usgs.earthquake.nshmp.netcdf.www;
+
+import java.nio.file.Path;
+
+import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
+import gov.usgs.earthquake.nshmp.netcdf.NetcdfHazardCurves;
+import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
+
+import io.micronaut.context.annotation.Value;
+import io.micronaut.context.event.StartupEvent;
+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.micronaut.runtime.event.annotation.EventListener;
+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 controller for getting static hazards or ground motions from a
+ * NetCDF file.
+ *
+ * @see NetcdfService
+ *
+ * @author U.S. Geological Survey
+ */
+@Tag(name = "Static Hazard Data")
+@Controller("/hazard")
+public class NetcdfController {
+
+  @Inject
+  private NshmpMicronautServlet servlet;
+
+  @Value("${nshmp-ws-static.netcdf-file}")
+  Path netcdfPath;
+
+  NetcdfServiceHazardCurves service;
+
+  /**
+   * Read in data type and return the appropriate service to use.
+   */
+  @EventListener
+  void startup(StartupEvent event) {
+    var netcdfHazard = new NetcdfHazardCurves(netcdfPath);
+    service = new NetcdfServiceHazardCurves(netcdfHazard);
+  }
+
+  /**
+   * GET method to return a static curve using URL query.
+   *
+   * @param request The HTTP request
+   * @param longitude The longitude of the site
+   * @param latitude Latitude of the site
+   * @param siteClass The site class (optional)
+   */
+  @Operation(
+      summary = "Returns curve(s) given a longitude, latitude, and/or a site class",
+      description = "Retrieve static curve(s) from a NSHM NetCDF file.\n\n" +
+          "For supported longitudes, latitudes, and site classes see the usage information.",
+      operationId = "netcdf_data_doGetHazard")
+  @ApiResponse(
+      description = "Returns a static curve from the NetCDF file",
+      responseCode = "200",
+      content = @Content(
+          schema = @Schema(type = "string")))
+  @Get(uri = "{?longitude,latitude,siteClass}", produces = MediaType.APPLICATION_JSON)
+  public HttpResponse<String> doGet(
+      HttpRequest<?> request,
+      @Schema(required = true) @QueryValue @Nullable Double longitude,
+      @Schema(required = true) @QueryValue @Nullable Double latitude,
+      @QueryValue @Nullable NehrpSiteClass siteClass) {
+    var query = new Query(longitude, latitude, siteClass);
+    return service.handleServiceCall(request, query);
+  }
+
+  /**
+   * GET method to return static curves using slash delimited.
+   *
+   * @param request The HTTP request
+   * @param longitude The longitude of the site
+   * @param latitude Latitude of the site
+   * @param siteClass The site class
+   */
+  @Operation(
+      summary = "Returns static curves given a longitude, latitude, and site class.",
+      description = "Retrieve static curves from a NetCDF file.\n\n" +
+          "For supported longitudes, latitudes, and site classes see the usage information.",
+      operationId = "netcdf_data_doGetHazardSlashWithSiteClass")
+  @ApiResponse(
+      description = "Returns static curves from the NetCDF file",
+      responseCode = "200",
+      content = @Content(
+          schema = @Schema(type = "string")))
+  @Get(uri = "/{longitude}/{latitude}/{siteClass}", produces = MediaType.APPLICATION_JSON)
+  public HttpResponse<String> doGetSlashBySite(
+      HttpRequest<?> request,
+      @Schema(required = true) @PathVariable @Nullable Double longitude,
+      @Schema(required = true) @PathVariable @Nullable Double latitude,
+      @Schema(required = true) @PathVariable @Nullable NehrpSiteClass siteClass) {
+    return doGet(request, longitude, latitude, siteClass);
+  }
+
+  /**
+   * GET method to return hazard curves using slash delimited.
+   *
+   * @param request The HTTP request
+   * @param longitude The longitude of the site
+   * @param latitude Latitude of the site
+   */
+  @Operation(
+      summary = "Returns static curves given a longitude and latitude.",
+      description = "Retrieve static curves from a NetCDF file.\n\n" +
+          "For supported longitudes and latitudes see the usage information.",
+      operationId = "netcdf_data_doGetHazardSlash")
+  @ApiResponse(
+      description = "Returns static curves from the NSHM NetCDF file",
+      responseCode = "200",
+      content = @Content(
+          schema = @Schema(type = "string")))
+  @Get(uri = "/{longitude}/{latitude}", produces = MediaType.APPLICATION_JSON)
+  public HttpResponse<String> doGetSlash(
+      HttpRequest<?> request,
+      @Schema(required = true) @PathVariable @Nullable Double longitude,
+      @Schema(required = true) @PathVariable @Nullable Double latitude) {
+    return doGet(request, longitude, latitude, null);
+  }
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceHazardCurves.java b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceHazardCurves.java
similarity index 95%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceHazardCurves.java
rename to src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceHazardCurves.java
index 51c5842d2fe0fd2beab761ed80e0ab5cd8fa1f62..7e3c78289dee965555ca9333bdd5048a569fcad7 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceHazardCurves.java
+++ b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceHazardCurves.java
@@ -10,9 +10,8 @@ import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
 import gov.usgs.earthquake.nshmp.netcdf.NetcdfHazardCurves;
 import gov.usgs.earthquake.nshmp.netcdf.data.StaticData;
 import gov.usgs.earthquake.nshmp.netcdf.data.StaticDataHazardCurves;
-import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfController.Query;
-import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfController.Service;
 import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfWsUtils.Key;
+import gov.usgs.earthquake.nshmp.netcdf.www.Query.Service;
 import gov.usgs.earthquake.nshmp.www.Response;
 import gov.usgs.earthquake.nshmp.www.WsUtils;
 import gov.usgs.earthquake.nshmp.www.meta.Status;
@@ -33,7 +32,7 @@ public class NetcdfServiceHazardCurves extends NetcdfService {
   static final String X_LABEL = "Ground Motion (g)";
   static final String Y_LABEL = "Annual Frequency of Exceedence";
 
-  NetcdfServiceHazardCurves(NetcdfHazardCurves netcdf) {
+  public NetcdfServiceHazardCurves(NetcdfHazardCurves netcdf) {
     super(netcdf);
   }
 
diff --git a/src/hazard/src/main/resources/application.yml b/src/hazard/src/main/resources/application.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a7d2854917d0a38f23ca7e248b2cdff455ac01b1
--- /dev/null
+++ b/src/hazard/src/main/resources/application.yml
@@ -0,0 +1,14 @@
+micronaut:
+  io:
+    watch:
+      paths: src
+      restart: true
+  router:
+    static-resources:
+      swagger:
+        enabled: true
+        paths: classpath:swagger
+        mapping: /**
+
+nshmp-ws-static:
+  netcdf-file: ${netcdf:src/main/resources/hazard-example.nc}
diff --git a/src/main/resources/hazard-example.nc b/src/hazard/src/main/resources/hazard-example.nc
similarity index 100%
rename from src/main/resources/hazard-example.nc
rename to src/hazard/src/main/resources/hazard-example.nc
diff --git a/src/hazard/src/main/resources/logback.xml b/src/hazard/src/main/resources/logback.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f74e41693090ae1b24cabe0642f4ec814564c4a1
--- /dev/null
+++ b/src/hazard/src/main/resources/logback.xml
@@ -0,0 +1,17 @@
+<configuration>
+
+  <appender name="STDOUT"
+    class="ch.qos.logback.core.ConsoleAppender">
+    <withJansi>true</withJansi>
+    <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder
+      by default -->
+    <encoder>
+      <pattern>%cyan(%d{HH:mm:ss.SSS}) %gray([%thread])
+        %highlight(%-5level) %magenta(%logger{36}) - %msg%n</pattern>
+    </encoder>
+  </appender>
+
+  <root level="info">
+    <appender-ref ref="STDOUT" />
+  </root>
+</configuration>
diff --git a/src/lib/build.gradle b/src/lib/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc
--- /dev/null
+++ b/src/lib/build.gradle
@@ -0,0 +1 @@
+
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfDataType.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfDataType.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfDataType.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfDataType.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/SiteClass.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/SiteClass.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/SiteClass.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/SiteClass.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/BoundingData.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/BoundingData.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/BoundingData.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/BoundingData.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfData.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfData.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfData.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfData.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfShape.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfShape.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfShape.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfShape.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/ScienceBaseInfo.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/ScienceBaseInfo.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/ScienceBaseInfo.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/ScienceBaseInfo.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/ScienceBaseMetadata.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/ScienceBaseMetadata.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/ScienceBaseMetadata.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/ScienceBaseMetadata.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/StaticData.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/StaticData.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/StaticData.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/StaticData.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReader.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReader.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReader.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReader.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java
similarity index 73%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java
index abedf45dfe5658fc40f45029b96c3b23bddf2bd7..d03775dd2fadd9c9a00baa1d7f79331f7d8c6e3a 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java
@@ -14,7 +14,6 @@ import gov.usgs.earthquake.nshmp.geo.Location;
 import gov.usgs.earthquake.nshmp.geo.LocationList;
 import gov.usgs.earthquake.nshmp.netcdf.data.BoundingData;
 import gov.usgs.earthquake.nshmp.netcdf.data.StaticData;
-import gov.usgs.earthquake.nshmp.netcdf.data.StaticDataHazardCurves;
 
 import ucar.ma2.DataType;
 import ucar.nc2.Group;
@@ -109,60 +108,13 @@ public class NetcdfUtils {
         });
   }
 
-  /**
-   * Check whether bounding hazards contain the same: Site classes, IMTs per
-   * each site class, and ground motions per each IMT
-   *
-   * @param a Bounding hazard map A
-   * @param b Bounding hazard map B
-   */
-  static void checkBoundingHazard(
-      StaticData<StaticDataHazardCurves> a,
-      StaticData<StaticDataHazardCurves> b) {
-    checkState(a.size() == b.size(), "Maps are not the same size");
-    checkState(a.keySet().containsAll(b.keySet()), "Site classes do not match");
-    a.keySet().forEach(key -> checkHazards(a.get(key), b.get(key)));
-  }
-
-  /**
-   * Checks bounding hazard maps contain the same: Site classes, IMTs per each
-   * site class, and ground motions per each IMT
-   *
-   * @param boundingData The bounding hazards
-   */
-  static void checkBoundingHazards(
-      BoundingData<StaticDataHazardCurves> boundingData,
-      Location location) {
-    checkArgument(boundingData.containsKey(location), "Location not in bounding hazards");
-    boundingData.keySet().stream()
-        .filter(loc -> loc.equals(location))
-        .forEach(key -> {
-          checkBoundingHazard(boundingData.get(location), boundingData.get(key));
-        });
-  }
-
-  /**
-   * Check whether hazards contain the same: IMTs and ground motions per each
-   * IMT
-   *
-   * @param a Hazard A
-   * @param b Hazard B
-   */
-  static void checkHazards(
-      StaticDataHazardCurves a,
-      StaticDataHazardCurves b) {
-    checkState(a.size() == b.size(), "Maps are not the same size");
-    checkState(a.keySet().containsAll(b.keySet()), "IMTs do not match");
-    a.keySet().forEach(key -> checkXySequence(a.get(key), b.get(key)));
-  }
-
   /**
    * Check that the X values are identical.
    *
    * @param a Sequence A
    * @param b Sequence B
    */
-  static void checkXySequence(XySequence a, XySequence b) {
+  public static void checkXySequence(XySequence a, XySequence b) {
     checkState(
         Arrays.equals(a.xValues().toArray(), b.xValues().toArray()),
         "Hazard curves xValues are not the same");
@@ -256,41 +208,6 @@ public class NetcdfUtils {
     return (String[]) get1DArray(group, key, DataType.STRING);
   }
 
-  /*
-   * Linear interpolation of data values to a target point
-   */
-  static StaticData<StaticDataHazardCurves> linearInterpolateHazardCurves(
-      StaticData<StaticDataHazardCurves> v1,
-      StaticData<StaticDataHazardCurves> v2,
-      double frac) {
-    checkBoundingHazard(v1, v2);
-
-    var targetMap = StaticData.<StaticDataHazardCurves> builder();
-
-    v1.keySet().forEach(siteClass -> {
-      var imtHazards = StaticDataHazardCurves.builder();
-      var v1StaticHazards = v1.get(siteClass);
-      var v2StaticHazards = v2.get(siteClass);
-
-      v1StaticHazards.keySet().forEach(imt -> {
-        var v1Haz = v1StaticHazards.get(imt).yValues().toArray();
-        var v2Haz = v2StaticHazards.get(imt).yValues().toArray();
-        var target = new double[v1Haz.length];
-
-        for (int i = 0; i < v1Haz.length; i++) {
-          target[i] = v1Haz[i] * (1 - frac) + v2Haz[i] * frac;
-        }
-
-        var xValues = v1StaticHazards.get(imt).xValues().toArray();
-        imtHazards.put(imt, XySequence.create(xValues, target));
-      });
-
-      targetMap.put(siteClass, imtHazards.build());
-    });
-
-    return targetMap.build();
-  }
-
   /*
    * Linear interpolation of data values to a target point
    */
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/Reader.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/Reader.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/Reader.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/Reader.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfService.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfService.java
similarity index 96%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfService.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfService.java
index 0453caa34d443a271a73fe8cc824611deaaa54e1..b3d161ec06019a733abd9551f4f5300011f203f7 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfService.java
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfService.java
@@ -11,8 +11,7 @@ import gov.usgs.earthquake.nshmp.geo.Location;
 import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
 import gov.usgs.earthquake.nshmp.netcdf.Netcdf;
 import gov.usgs.earthquake.nshmp.netcdf.data.ScienceBaseMetadata;
-import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfController.Query;
-import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfController.Service;
+import gov.usgs.earthquake.nshmp.netcdf.www.Query.Service;
 import gov.usgs.earthquake.nshmp.netcdf.www.meta.DoubleParameter;
 import gov.usgs.earthquake.nshmp.www.Response;
 
@@ -88,7 +87,7 @@ public abstract class NetcdfService {
    * @param httpRequest The HTTP request
    * @param query The service query
    */
-  HttpResponse<String> handleServiceCall(HttpRequest<?> httpRequest, Query query) {
+  public HttpResponse<String> handleServiceCall(HttpRequest<?> httpRequest, Query query) {
     try {
       var url = httpRequest.getUri().toString();
       LOGGER.info("Request: " + url);
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfWsUtils.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfWsUtils.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfWsUtils.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfWsUtils.java
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/Query.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/Query.java
new file mode 100644
index 0000000000000000000000000000000000000000..c1a5bffee4dfabd6cde7fcfa6293257ba248959d
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/Query.java
@@ -0,0 +1,21 @@
+package gov.usgs.earthquake.nshmp.netcdf.www;
+
+import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
+
+public class Query {
+  public final Double longitude;
+  public final Double latitude;
+  public final NehrpSiteClass siteClass;
+
+  public Query(Double longitude, Double latitude, NehrpSiteClass siteClass) {
+    this.longitude = longitude;
+    this.latitude = latitude;
+    this.siteClass = siteClass;
+  }
+
+  public static enum Service {
+    BOUNDING,
+    CURVES,
+    CURVES_BY_SITE_CLASS,
+  }
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/SwaggerController.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/SwaggerController.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/SwaggerController.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/SwaggerController.java
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/meta/DoubleParameter.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/meta/DoubleParameter.java
similarity index 100%
rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/meta/DoubleParameter.java
rename to src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/meta/DoubleParameter.java
diff --git a/src/main/resources/application.yml b/src/lib/src/main/resources/application.yml
similarity index 100%
rename from src/main/resources/application.yml
rename to src/lib/src/main/resources/application.yml
diff --git a/src/main/resources/eclipse.importorder b/src/lib/src/main/resources/eclipse.importorder
similarity index 100%
rename from src/main/resources/eclipse.importorder
rename to src/lib/src/main/resources/eclipse.importorder
diff --git a/src/lib/src/main/resources/hazard-example.nc b/src/lib/src/main/resources/hazard-example.nc
new file mode 100644
index 0000000000000000000000000000000000000000..521866c0689b6ad029a2bfd7f3461ad9e69363f4
Binary files /dev/null and b/src/lib/src/main/resources/hazard-example.nc differ
diff --git a/src/lib/src/main/resources/logback.xml b/src/lib/src/main/resources/logback.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f74e41693090ae1b24cabe0642f4ec814564c4a1
--- /dev/null
+++ b/src/lib/src/main/resources/logback.xml
@@ -0,0 +1,17 @@
+<configuration>
+
+  <appender name="STDOUT"
+    class="ch.qos.logback.core.ConsoleAppender">
+    <withJansi>true</withJansi>
+    <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder
+      by default -->
+    <encoder>
+      <pattern>%cyan(%d{HH:mm:ss.SSS}) %gray([%thread])
+        %highlight(%-5level) %magenta(%logger{36}) - %msg%n</pattern>
+    </encoder>
+  </appender>
+
+  <root level="info">
+    <appender-ref ref="STDOUT" />
+  </root>
+</configuration>
diff --git a/src/main/resources/nshmp.eclipse-format.xml b/src/lib/src/main/resources/nshmp.eclipse-format.xml
similarity index 100%
rename from src/main/resources/nshmp.eclipse-format.xml
rename to src/lib/src/main/resources/nshmp.eclipse-format.xml
diff --git a/src/lib/src/main/resources/rtsa-example.nc b/src/lib/src/main/resources/rtsa-example.nc
new file mode 100644
index 0000000000000000000000000000000000000000..0b4e8d883e15107bd60ed7fc8bde3a9da0066427
Binary files /dev/null and b/src/lib/src/main/resources/rtsa-example.nc differ
diff --git a/src/main/resources/swagger/favicon.ico b/src/lib/src/main/resources/swagger/favicon.ico
similarity index 100%
rename from src/main/resources/swagger/favicon.ico
rename to src/lib/src/main/resources/swagger/favicon.ico
diff --git a/src/main/resources/swagger/index.css b/src/lib/src/main/resources/swagger/index.css
similarity index 100%
rename from src/main/resources/swagger/index.css
rename to src/lib/src/main/resources/swagger/index.css
diff --git a/src/main/resources/swagger/index.html b/src/lib/src/main/resources/swagger/index.html
similarity index 100%
rename from src/main/resources/swagger/index.html
rename to src/lib/src/main/resources/swagger/index.html
diff --git a/src/main/resources/swagger/index.js b/src/lib/src/main/resources/swagger/index.js
similarity index 100%
rename from src/main/resources/swagger/index.js
rename to src/lib/src/main/resources/swagger/index.js
diff --git a/src/main/resources/swagger/usgs-logo.svg b/src/lib/src/main/resources/swagger/usgs-logo.svg
similarity index 100%
rename from src/main/resources/swagger/usgs-logo.svg
rename to src/lib/src/main/resources/swagger/usgs-logo.svg
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/netcdf/SiteClassTests.java b/src/lib/src/test/java/gov/usgs/earthquake/nshmp/netcdf/SiteClassTests.java
similarity index 100%
rename from src/test/java/gov/usgs/earthquake/nshmp/netcdf/SiteClassTests.java
rename to src/lib/src/test/java/gov/usgs/earthquake/nshmp/netcdf/SiteClassTests.java
diff --git a/src/test/resources/invalid-netcdf-file.nc b/src/lib/src/test/resources/invalid-netcdf-file.nc
similarity index 100%
rename from src/test/resources/invalid-netcdf-file.nc
rename to src/lib/src/test/resources/invalid-netcdf-file.nc
diff --git a/src/test/resources/map-netcdf-test-0p05.geojson b/src/lib/src/test/resources/map-netcdf-test-0p05.geojson
similarity index 100%
rename from src/test/resources/map-netcdf-test-0p05.geojson
rename to src/lib/src/test/resources/map-netcdf-test-0p05.geojson
diff --git a/src/test/resources/nshmp-conus-test-fv.0.3.nc b/src/lib/src/test/resources/nshmp-conus-test-fv.0.3.nc
similarity index 100%
rename from src/test/resources/nshmp-conus-test-fv.0.3.nc
rename to src/lib/src/test/resources/nshmp-conus-test-fv.0.3.nc
diff --git a/src/test/resources/nshmp-conus-test-fv.0.4.nc b/src/lib/src/test/resources/nshmp-conus-test-fv.0.4.nc
similarity index 100%
rename from src/test/resources/nshmp-conus-test-fv.0.4.nc
rename to src/lib/src/test/resources/nshmp-conus-test-fv.0.4.nc
diff --git a/src/test/resources/nshmp-conus-test-fv.1.0.nc b/src/lib/src/test/resources/nshmp-conus-test-fv.1.0.nc
similarity index 100%
rename from src/test/resources/nshmp-conus-test-fv.1.0.nc
rename to src/lib/src/test/resources/nshmp-conus-test-fv.1.0.nc