diff --git a/.gitignore b/.gitignore
index df0ac6ee95199549db212411b7a64c02beb4bdec..099780875bb4fb4122abdb4b7ea9216ee354be77 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@ config.properties
 libs
 .factorypath
 .apt_generated*
+*version.json
 
 # Node
 node_modules
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9c76c4088c555b97382dcb780fb1e4ef1d422ef6..949b1dc9524e98a0a2a3226c49a4cac7a2037960 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -166,7 +166,9 @@ CHS Registry:
     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-haz
 
 Container Registry:
@@ -190,7 +192,9 @@ Container Registry:
     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-haz
 
 Build Project:
diff --git a/Dockerfile b/Dockerfile
index 367f83e95d3a793a30959e3a748031b03b20d332..7a5ba31d4ec3d4dcc2968e770dd696257b5d88a9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -39,6 +39,8 @@ FROM ${BUILD_IMAGE} as builder
 # Remove once 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 5a8318fc9b25bf108490fe2674ffbf6d7697fcd9..684942bb13d93b773bd75aa44f9fd91c2e874714 100644
--- a/build.gradle
+++ b/build.gradle
@@ -33,6 +33,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 "io.micronaut.application" version "${micronautPluginVersion}"
@@ -40,6 +41,7 @@ plugins {
   id "maven-publish"
 }
 
+apply from: "${projectDir}/gradle/app-version.gradle"
 apply from: "${projectDir}/gradle/dependencies.gradle"
 apply from: "${projectDir}/gradle/ext.gradle"
 apply from: "${projectDir}/gradle/jar.gradle"
diff --git a/gradle.properties b/gradle.properties
index ac6c4824c47c1820e4d0b3a9c397c4d3e331098b..d3bac3e39e1fe7efbc5750136b5f98df442e5e9a 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,6 +3,7 @@ awsLambdaCoreVersion = 1.1.0
 awsLambdaVersion = 1.11.461
 awsS3Version = 1.11.579
 githooksVersion = 1.2.0
+gitVersionVersion = 0.15.0
 jacksonVersion = 2.9.0
 junitVersion = 5.8.2
 micronautVersion = 3.2.3
@@ -10,9 +11,9 @@ micronautRxVersion = 2.1.1
 micronautPluginVersion = 3.1.1
 nodePluginVersion = 3.0.1
 nodeVersion = 16.3.0
-nshmpLibVersion = 1.0.4
-nshmpWsUtilsVersion = 0.3.5
+nshmpLibVersion = 1.0.6
+nshmpWsUtilsVersion = 0.3.7
 shadowVersion = 7.1.2
 spotbugsVersion = 4.7.0
 spotlessVersion = 6.0.4
-swaggerVersion = 2.1.7
+swaggerVersion = 2.2.0
diff --git a/gradle/app-version.gradle b/gradle/app-version.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..2367ef4006a7789f8bb00b7402d326522a5f04f9
--- /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-haz-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: project.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/src/main/java/gov/usgs/earthquake/nshmp/www/HazVersion.java b/src/main/java/gov/usgs/earthquake/nshmp/www/HazVersion.java
new file mode 100644
index 0000000000000000000000000000000000000000..c8ad4aecb2533d855fda9393eee13feeecfcf17c
--- /dev/null
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/HazVersion.java
@@ -0,0 +1,23 @@
+package gov.usgs.earthquake.nshmp.www;
+
+import com.google.common.io.Resources;
+
+import gov.usgs.earthquake.nshmp.internal.AppVersion;
+import gov.usgs.earthquake.nshmp.internal.LibVersion;
+
+public class HazVersion implements AppVersion {
+
+  public static VersionInfo[] appVersions() {
+    VersionInfo[] versions = {
+        new HazVersion().getVersionInfo(),
+        new LibVersion().getVersionInfo(),
+        new WsUtilsVersion().getVersionInfo(),
+    };
+    return versions;
+  }
+
+  public VersionInfo getVersionInfo() {
+    var resource = Resources.getResource("version/nshmp-haz-version.json");
+    return AppVersion.versionInfo(resource);
+  }
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java b/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java
index 449c5ec3a3dce6c42291dc8f652ba32d9e2839ec..4896b9c81f8cc55e173db2bfe2b0b29a8cff94a6 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java
@@ -166,6 +166,7 @@ public class ServletUtil {
     var svcResponse = ResponseBody.error()
         .name(name)
         .url(url)
+        .metadata(new ResponseMetadata(HazVersion.appVersions()))
         .request(url)
         .response(msg)
         .build();
@@ -206,8 +207,7 @@ public class ServletUtil {
     Server(int threads, Stopwatch timer) {
       this.threads = threads;
       this.timer = timer.toString();
-      this.version = "TODO where to get version?";
+      this.version = new HazVersion().getVersionInfo().version;
     }
   }
-
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java
index a4e527bf761e48133bd97b69362b0d5b5591f4c9..8a41630eafe3eb942596e0562f42a12ce8026f0a 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java
@@ -28,7 +28,9 @@ import gov.usgs.earthquake.nshmp.calc.Site;
 import gov.usgs.earthquake.nshmp.geo.Location;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
+import gov.usgs.earthquake.nshmp.www.HazVersion;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ResponseMetadata;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.hazard.HazardService.Metadata;
 import gov.usgs.earthquake.nshmp.www.meta.Parameter;
@@ -73,6 +75,7 @@ public final class DisaggService {
     var response = ResponseBody.usage()
         .name(NAME)
         .url(url)
+        .metadata(new ResponseMetadata(HazVersion.appVersions()))
         .request(url)
         .response(usage)
         .build();
@@ -93,6 +96,7 @@ public final class DisaggService {
     var body = ResponseBody.success()
         .name(NAME)
         .url(request.http.getUri().toString())
+        .metadata(new ResponseMetadata(HazVersion.appVersions()))
         .request(request)
         .response(response)
         .build();
@@ -113,6 +117,7 @@ public final class DisaggService {
     var body = ResponseBody.success()
         .name(NAME)
         .url(request.http.getUri().toString())
+        .metadata(new ResponseMetadata(HazVersion.appVersions()))
         .request(request)
         .response(response)
         .build();
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
index e87db5fef77f5f75b96880e89d03e3b3d49ab003..c324da5cf9afb08e4d3199e09f7b265a23480fa4 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
@@ -34,7 +34,9 @@ import gov.usgs.earthquake.nshmp.geo.Location;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
 import gov.usgs.earthquake.nshmp.model.SourceType;
+import gov.usgs.earthquake.nshmp.www.HazVersion;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ResponseMetadata;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter;
 import gov.usgs.earthquake.nshmp.www.meta.Parameter;
@@ -92,6 +94,7 @@ public final class HazardService {
     var body = ResponseBody.usage()
         .name(NAME)
         .url(url)
+        .metadata(new ResponseMetadata(HazVersion.appVersions()))
         .request(url)
         .response(usage)
         .build();
@@ -112,6 +115,7 @@ public final class HazardService {
     var body = ResponseBody.success()
         .name(NAME)
         .url(request.http.getUri().toString())
+        .metadata(new ResponseMetadata(HazVersion.appVersions()))
         .request(request)
         .response(response)
         .build();
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java
index c0bcb47fd4159c8b8171dabbff526ef1f4c37d7b..b4ce06c52b3314701941246fa723215577df9967 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java
@@ -18,8 +18,10 @@ import gov.usgs.earthquake.nshmp.calc.EqRate;
 import gov.usgs.earthquake.nshmp.calc.Site;
 import gov.usgs.earthquake.nshmp.geo.Location;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
+import gov.usgs.earthquake.nshmp.www.HazVersion;
 import gov.usgs.earthquake.nshmp.www.RateController;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ResponseMetadata;
 import gov.usgs.earthquake.nshmp.www.ServicesUtil.Key;
 import gov.usgs.earthquake.nshmp.www.ServicesUtil.ServiceQueryData;
 import gov.usgs.earthquake.nshmp.www.ServicesUtil.ServiceRequestData;
@@ -107,6 +109,7 @@ public final class RateService {
     return ResponseBody.<String, Usage> usage()
         .name(service.name)
         .url(url)
+        .metadata(new ResponseMetadata(HazVersion.appVersions()))
         .request(url)
         .response(usage)
         .build();
@@ -118,10 +121,11 @@ public final class RateService {
       RequestData data) throws InterruptedException, ExecutionException {
     var timer = Stopwatch.createStarted();
     var rates = calc(service, data);
-    var responseData = new ResponseData(new ResponseMetadata(service, data), rates, timer);
+    var responseData = new ResponseData(new ServiceResponseMetadata(service, data), rates, timer);
     return ResponseBody.<RequestData, ResponseData> success()
         .name(service.name)
         .request(data)
+        .metadata(new ResponseMetadata(HazVersion.appVersions()))
         .response(responseData)
         .url(request.getUri().getPath())
         .build();
@@ -249,7 +253,7 @@ public final class RateService {
     }
   }
 
-  private static final class ResponseMetadata {
+  private static final class ServiceResponseMetadata {
     final double latitude;
     final double longitude;
     final double distance;
@@ -258,7 +262,7 @@ public final class RateService {
     final String xlabel = "Magnitude (Mw)";
     final String ylabel;
 
-    ResponseMetadata(Service service, RequestData request) {
+    ServiceResponseMetadata(Service service, RequestData request) {
       var isProbability = service == Service.PROBABILITY;
       this.longitude = request.longitude;
       this.latitude = request.latitude;
@@ -270,10 +274,10 @@ public final class RateService {
 
   private static final class ResponseData {
     final Object server;
-    final ResponseMetadata metadata;
+    final ServiceResponseMetadata metadata;
     final List<Sequence> data;
 
-    ResponseData(ResponseMetadata metadata, EqRate rates, Stopwatch timer) {
+    ResponseData(ServiceResponseMetadata metadata, EqRate rates, Stopwatch timer) {
       server = ServletUtil.serverData(ServletUtil.THREAD_COUNT, timer);
       this.metadata = metadata;
       this.data = buildSequence(rates);
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java
index 01e2d4c2186ce6f09a74094576a6b9f489a34e11..5525e39647022f1523a0969ba0201a94de5fd8bd 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java
@@ -4,7 +4,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import gov.usgs.earthquake.nshmp.model.Models;
+import gov.usgs.earthquake.nshmp.www.HazVersion;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ResponseMetadata;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.SourceLogicTreesController;
 
@@ -33,6 +35,7 @@ public class SourceLogicTreesService {
       var response = ResponseBody.success()
           .name(NAME)
           .url(url)
+          .metadata(new ResponseMetadata(HazVersion.appVersions()))
           .request(url)
           .response(trees)
           .build();
@@ -52,6 +55,7 @@ public class SourceLogicTreesService {
       var response = ResponseBody.success()
           .name(NAME)
           .url(url)
+          .metadata(new ResponseMetadata(HazVersion.appVersions()))
           .request(requestData)
           .response(tree)
           .build();
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java
index 03704247e5c02916c38503d6303c4516a67b2c63..f72a1546e820b09e6283116236b4f415edef8823 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java
@@ -17,7 +17,9 @@ import gov.usgs.earthquake.nshmp.gmm.Gmm;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
+import gov.usgs.earthquake.nshmp.www.HazVersion;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ResponseMetadata;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.WsUtils;
 import gov.usgs.earthquake.nshmp.www.meta.Parameter;
@@ -59,6 +61,7 @@ public class SourceServices {
       var response = ResponseBody.usage()
           .name(NAME)
           .url(url)
+          .metadata(new ResponseMetadata(HazVersion.appVersions()))
           .request(url)
           .response(new ResponseData())
           .build();
diff --git a/ws.Dockerfile b/ws.Dockerfile
index 4d1e9f44b51e47769210bb9ed2ea87252c669be8..cae23f1bba8f23892a8033c0ea9733acc0f1cd7e 100644
--- a/ws.Dockerfile
+++ b/ws.Dockerfile
@@ -27,6 +27,8 @@ ARG builder_workdir
 # Remove once 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