diff --git a/.gitignore b/.gitignore
index d74a7d631eb3807929f1b17cff07e2752478bcf8..1a36c0be0f19a3a1ed276885dc482cbc1e8cde00 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,7 @@ libs
 .factorypath
 .apt_*
 .vscode
+*version.json
 
 # Node
 node_modules
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9a9c1fedcb1a5a6be89ddf0d10db6ec07e67fce0..bf51fe72f54932d6f7290d3e91302a3e02f7f0dc 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -160,7 +160,9 @@ Build Image:
     DOCKER_BUILD_ARGS: |
       BUILD_IMAGE=${DEVOPS_REGISTRY}usgs/amazoncorretto:11
       FROM_IMAGE=${DEVOPS_REGISTRY}usgs/amazoncorretto:11
+      CI_COMMIT_BRANCH=${CI_COMMIT_BRANCH}
       CI_JOB_TOKEN=${CI_JOB_TOKEN}
+      CI_PROJECT_URL=${CI_PROJECT_URL}
     UPSTREAM_PATH: ghsc/nshmp/nshmp-ws-static
 
 Build Project:
diff --git a/Dockerfile b/Dockerfile
index c363c429a6856e6d6ff3c2239f0c9686950b6ad4..70a507a3596ae16bdfdc22b8ec5e91be70bae9a2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -13,6 +13,8 @@ FROM ${BUILD_IMAGE} as builder
 # TODO: Token needed until nshmp-lib is public
 ARG GITLAB_TOKEN=null
 ARG CI_JOB_TOKEN=null
+ARG CI_PROJECT_URL=null
+ARG CI_COMMIT_BRANCH=null
 
 WORKDIR /app
 
diff --git a/build.gradle b/build.gradle
index 13c5a81698e1bd06cc7a401fa3222a1d73855c39..32482cd114a1490d8da7a14866732cad9778e6ab 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,6 +3,7 @@ plugins {
   id "com.github.johnrengelman.shadow" version "${shadowVersion}"
   id "com.github.node-gradle.node" version "${nodePluginVersion}"
   id "com.github.spotbugs" version "${spotbugsVersion}"
+  id "com.palantir.git-version" version "${gitVersionVersion}"
   id "com.star-zero.gradle.githook" version "${githooksVersion}"
   id "eclipse-wtp"
   id "jacoco"
@@ -26,6 +27,7 @@ subprojects {
   apply plugin: "com.github.johnrengelman.shadow"
   apply plugin: "com.github.node-gradle.node"
   apply plugin: "com.github.spotbugs"
+  apply plugin: "com.palantir.git-version"
   apply plugin: "com.star-zero.gradle.githook"
   apply plugin: "com.github.johnrengelman.shadow"
   apply plugin: "eclipse-wtp"
@@ -41,6 +43,7 @@ subprojects {
   sourceCompatibility = JavaVersion.VERSION_11
   targetCompatibility = JavaVersion.VERSION_11
 
+  apply from: "${rootDir}/gradle/app-version.gradle"
   apply from: "${rootDir}/gradle/dependencies.gradle"
   apply from: "${rootDir}/gradle/git-hooks.gradle"
   apply from: "${rootDir}/gradle/repositories.gradle"
diff --git a/gradle.properties b/gradle.properties
index a1b97d47b61eabba4fd55dbe3b84b0613ab1577f..026e74f91287268271a8bf58555e25972de748e3 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,5 @@
 githooksVersion = 1.2.0
+gitVersionVersion = 0.15.0
 jacksonVersion = 2.9.0
 junitVersion = 5.8.2
 micronautVersion = 3.2.3
@@ -6,8 +7,8 @@ micronautRxVersion = 2.1.1
 netcdfVersion = 5.5.2
 nodePluginVersion = 3.0.1
 nodeVersion = 16.3.0
-nshmpLibVersion = 0.8.2
-nshmpWsUtilsVersion = 0.3.4
+nshmpLibVersion = 1.0.6
+nshmpWsUtilsVersion = 0.3.7
 openApiVersion = 4.0.0
 shadowVersion = 7.1.1
 slfVersion = 1.7.30
diff --git a/gradle/app-version.gradle b/gradle/app-version.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..3022638738f2eeb93238901b44222561affd3f6b
--- /dev/null
+++ b/gradle/app-version.gradle
@@ -0,0 +1,24 @@
+apply plugin: "com.palantir.git-version"
+
+tasks.withType(JavaCompile) {
+  doFirst {
+    def versionFile = new File("${projectDir}/src/main/resources/version/nshmp-ws-static-version.json")
+    new File(versionFile.getParent()).mkdirs()
+    def details = versionDetails()
+    def ciProjectUrl = System.getenv("CI_PROJECT_URL")
+    def branch = System.getenv("CI_COMMIT_BRANCH")
+    def versionInfo = [
+      branchName: branch ? branch : details.branchName,
+      commitDistance: details.commitDistance,
+      gitHash:  details.gitHash,
+      gitHashFull:  details.gitHashFull,
+      isCleanTag:  details.isCleanTag,
+      lastTag:  details.lastTag,
+      projectName: rootProject.name,
+      url: ciProjectUrl ? ciProjectUrl : 'git config --get remote.origin.url'.execute().text.replace('\n', ''),
+      version: details.version,
+    ]
+    def json = groovy.json.JsonOutput.toJson(versionInfo)
+    versionFile.write(groovy.json.JsonOutput.prettyPrint(json))
+  }
+}
diff --git a/gradle/spotless.gradle b/gradle/spotless.gradle
index 2025f24761e9fb70d1a8d20216d5ac1066b445d8..d4ea0161bd5bba5db10090b8a7bca2804349596c 100644
--- a/gradle/spotless.gradle
+++ b/gradle/spotless.gradle
@@ -59,7 +59,8 @@ spotless {
     target fileTree(".") {
       include "**/*.xml"
       exclude "**/build", ".settings", ".classpath", ".project",
-          "tmp", ".gradle", "libs", "node_modules", "**/.gradle", "bin", "**/bin"
+          "tmp", ".gradle", "libs", "node_modules", "**/.gradle",
+          "bin", "**/bin", "**/.settings"
     }
     eclipseWtp("xml")
     trimTrailingWhitespace()
diff --git a/src/aashto/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
index 1934308e15e35ad52760cbba7849c145ae02f5f2..da5c6c907be9e73d1087b63699e3142b109b53ee 100644
--- a/src/aashto/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
@@ -6,7 +6,7 @@ import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
 import gov.usgs.earthquake.nshmp.netcdf.NetcdfGroundMotions;
 import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfService.RequestDataSiteClass;
 import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfService.ResponseData;
-import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfService.ResponseMetadata;
+import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfService.ServiceResponseMetadata;
 import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
 
@@ -152,5 +152,5 @@ public class NetcdfController {
   // For Swagger schema
   private static class Response
       extends
-      ResponseBody<RequestDataSiteClass, ResponseData<ResponseMetadata>> {}
+      ResponseBody<RequestDataSiteClass, ResponseData<ServiceResponseMetadata>> {}
 }
diff --git a/src/aashto/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
index ddf9aa20b9a59977201db3b4020e5d9f5eaf49f5..5b1d333ca294a59f5e102c2fcd05c36afa2c50c0 100644
--- a/src/aashto/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
@@ -6,10 +6,12 @@ import java.util.stream.Collectors;
 import gov.usgs.earthquake.nshmp.data.XySequence;
 import gov.usgs.earthquake.nshmp.geo.Location;
 import gov.usgs.earthquake.nshmp.netcdf.NetcdfGroundMotions;
+import gov.usgs.earthquake.nshmp.netcdf.NetcdfVersion;
 import gov.usgs.earthquake.nshmp.netcdf.data.StaticData;
 import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfWsUtils.Key;
 import gov.usgs.earthquake.nshmp.netcdf.www.Query.Service;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ResponseMetadata;
 import gov.usgs.earthquake.nshmp.www.WsUtils;
 
 import io.micronaut.http.HttpRequest;
@@ -37,6 +39,7 @@ public class NetcdfServiceGroundMotions extends NetcdfService<Query> {
     var metadata = new Metadata(request, SERVICE_DESCRIPTION);
 
     return ResponseBody.<String, Metadata> usage()
+        .metadata(new ResponseMetadata(NetcdfVersion.appVersions()))
         .name(SERVICE_NAME)
         .request(NetcdfWsUtils.getRequestUrl(request))
         .response(metadata)
@@ -78,7 +81,7 @@ public class NetcdfServiceGroundMotions extends NetcdfService<Query> {
   }
 
   @Override
-  ResponseBody<RequestDataSiteClass, ResponseData<ResponseMetadata>> processCurvesSiteClass(
+  ResponseBody<RequestDataSiteClass, ResponseData<ServiceResponseMetadata>> processCurvesSiteClass(
       RequestDataSiteClass request,
       String url) {
     WsUtils.checkValue(Key.LATITUDE, request.latitude);
@@ -87,7 +90,8 @@ public class NetcdfServiceGroundMotions extends NetcdfService<Query> {
     var curves = netcdf().staticData(request.site, request.siteClass);
     var responseData = toResponseData(request, curves);
 
-    return ResponseBody.<RequestDataSiteClass, ResponseData<ResponseMetadata>> success()
+    return ResponseBody.<RequestDataSiteClass, ResponseData<ServiceResponseMetadata>> success()
+        .metadata(new ResponseMetadata(NetcdfVersion.appVersions()))
         .name(SERVICE_NAME)
         .request(request)
         .response(responseData)
@@ -96,7 +100,7 @@ public class NetcdfServiceGroundMotions extends NetcdfService<Query> {
   }
 
   @Override
-  ResponseBody<RequestData, List<ResponseData<ResponseMetadata>>> processCurves(
+  ResponseBody<RequestData, List<ResponseData<ServiceResponseMetadata>>> processCurves(
       RequestData request,
       String url) {
     WsUtils.checkValue(Key.LATITUDE, request.latitude);
@@ -104,7 +108,8 @@ public class NetcdfServiceGroundMotions extends NetcdfService<Query> {
     var curves = netcdf().staticData(request.site);
     var responseData = toList(request.site, curves);
 
-    return ResponseBody.<RequestData, List<ResponseData<ResponseMetadata>>> success()
+    return ResponseBody.<RequestData, List<ResponseData<ServiceResponseMetadata>>> success()
+        .metadata(new ResponseMetadata(NetcdfVersion.appVersions()))
         .name(SERVICE_NAME)
         .request(request)
         .response(responseData)
@@ -112,7 +117,7 @@ public class NetcdfServiceGroundMotions extends NetcdfService<Query> {
         .build();
   }
 
-  List<ResponseData<ResponseMetadata>> toList(
+  List<ResponseData<ServiceResponseMetadata>> toList(
       Location site,
       StaticData<XySequence> curves) {
     return curves.entrySet().stream()
@@ -123,10 +128,10 @@ public class NetcdfServiceGroundMotions extends NetcdfService<Query> {
         .collect(Collectors.toList());
   }
 
-  ResponseData<ResponseMetadata> toResponseData(
+  ResponseData<ServiceResponseMetadata> toResponseData(
       RequestDataSiteClass request,
       XySequence curves) {
-    var metadata = new ResponseMetadata(
+    var metadata = new ServiceResponseMetadata(
         request.siteClass,
         X_LABEL,
         Y_LABEL);
diff --git a/src/hazard/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
index b223f4e66e541cf2619cb748409ca171a27211df..94a1d74c36e8bf2e72ffd0c828a36d61ce719fb6 100644
--- a/src/hazard/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
@@ -8,11 +8,13 @@ import gov.usgs.earthquake.nshmp.geo.Location;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
 import gov.usgs.earthquake.nshmp.netcdf.NetcdfHazardCurves;
+import gov.usgs.earthquake.nshmp.netcdf.NetcdfVersion;
 import gov.usgs.earthquake.nshmp.netcdf.data.StaticData;
 import gov.usgs.earthquake.nshmp.netcdf.data.StaticDataHazardCurves;
 import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfWsUtils.Key;
 import gov.usgs.earthquake.nshmp.netcdf.www.Query.Service;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ResponseMetadata;
 import gov.usgs.earthquake.nshmp.www.WsUtils;
 
 import io.micronaut.http.HttpRequest;
@@ -39,6 +41,7 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
   ResponseBody<String, Metadata> getMetadataResponse(HttpRequest<?> request) {
     var metadata = new Metadata(request, SERVICE_DESCRIPTION);
     return ResponseBody.<String, Metadata> usage()
+        .metadata(new ResponseMetadata(NetcdfVersion.appVersions()))
         .name(SERVICE_NAME)
         .request(NetcdfWsUtils.getRequestUrl(request))
         .response(metadata)
@@ -82,6 +85,7 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
     var curvesAsList = toList(request.site, curves);
 
     return ResponseBody.<RequestData, List<List<ResponseData<HazardResponseMetadata>>>> success()
+        .metadata(new ResponseMetadata(NetcdfVersion.appVersions()))
         .name(SERVICE_NAME)
         .request(request)
         .response(curvesAsList)
@@ -100,6 +104,7 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
     var curvesAsList = toList(request, curves);
 
     return ResponseBody.<RequestDataSiteClass, List<ResponseData<HazardResponseMetadata>>> success()
+        .metadata(new ResponseMetadata(NetcdfVersion.appVersions()))
         .name(SERVICE_NAME)
         .request(request)
         .response(curvesAsList)
@@ -117,6 +122,7 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
     var curves = netcdf().staticData(request.site, request.siteClass);
 
     return ResponseBody.<RequestDataSiteClass, ResponseData<HazardResponseMetadata>> success()
+        .metadata(new ResponseMetadata(NetcdfVersion.appVersions()))
         .name(SERVICE_NAME)
         .request(request)
         .response(toResponseData(request, request.imt, curves.get(request.imt)))
@@ -182,7 +188,7 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
     }
   }
 
-  static class HazardResponseMetadata extends ResponseMetadata {
+  static class HazardResponseMetadata extends ServiceResponseMetadata {
     public final Imt imt;
 
     HazardResponseMetadata(NehrpSiteClass siteClass, Imt imt, String xLabel, String yLabel) {
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfVersion.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfVersion.java
new file mode 100644
index 0000000000000000000000000000000000000000..cda7eecd4a7a91cece6bc327f9d1bb5703a5944f
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfVersion.java
@@ -0,0 +1,24 @@
+package gov.usgs.earthquake.nshmp.netcdf;
+
+import com.google.common.io.Resources;
+
+import gov.usgs.earthquake.nshmp.internal.AppVersion;
+import gov.usgs.earthquake.nshmp.internal.LibVersion;
+import gov.usgs.earthquake.nshmp.www.WsUtilsVersion;
+
+public class NetcdfVersion implements AppVersion {
+
+  public static VersionInfo[] appVersions() {
+    VersionInfo[] versions = {
+        new NetcdfVersion().getVersionInfo(),
+        new LibVersion().getVersionInfo(),
+        new WsUtilsVersion().getVersionInfo(),
+    };
+    return versions;
+  }
+
+  public VersionInfo getVersionInfo() {
+    var resource = Resources.getResource("version/nshmp-ws-static-version.json");
+    return AppVersion.versionInfo(resource);
+  }
+}
diff --git a/src/lib/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
index e487f79b241d4c9ed5e55291a38e1a6b19fcd2b6..c6f12ebcb46b5442b27c176e4fca3979fc84bacc 100644
--- a/src/lib/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
@@ -197,12 +197,12 @@ public abstract class NetcdfService<T extends Query> {
     }
   }
 
-  static class ResponseMetadata {
+  static class ServiceResponseMetadata {
     public NehrpSiteClass siteClass;
     public String xLabel;
     public String yLabel;
 
-    ResponseMetadata(
+    ServiceResponseMetadata(
         NehrpSiteClass siteClass,
         String xLabel,
         String yLabel) {
@@ -212,7 +212,7 @@ public abstract class NetcdfService<T extends Query> {
     }
   }
 
-  static class ResponseData<T extends ResponseMetadata> {
+  static class ResponseData<T extends ServiceResponseMetadata> {
     final T metadata;
     final XySequence data;
 
diff --git a/src/lib/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
index 83ac0be052b7eb5c05829a310588ecdad126754c..70216205cdd87b608250c53141bdd8bb479d4429 100644
--- a/src/lib/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
@@ -13,9 +13,11 @@ import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 
 import gov.usgs.earthquake.nshmp.gmm.Imt;
+import gov.usgs.earthquake.nshmp.netcdf.NetcdfVersion;
 import gov.usgs.earthquake.nshmp.netcdf.data.NetcdfData;
 import gov.usgs.earthquake.nshmp.netcdf.data.ScienceBaseMetadata;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ResponseMetadata;
 import gov.usgs.earthquake.nshmp.www.SwaggerUtils;
 import gov.usgs.earthquake.nshmp.www.WsUtils.EnumSerializer;
 import gov.usgs.earthquake.nshmp.www.WsUtils.NaNSerializer;
@@ -55,6 +57,7 @@ public class NetcdfWsUtils {
       String url) {
     var msg = e.getMessage() + " (see logs)";
     var svcResponse = ResponseBody.error()
+        .metadata(new ResponseMetadata(NetcdfVersion.appVersions()))
         .name(name)
         .request(url)
         .response(msg)