diff --git a/.gitignore b/.gitignore
index 8d1237456a5ea0661a773992085a454c9e20ac95..60aefc922ab07cf7aaf22c9c895917f5c4ccef3f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ tmp
 .factorypath
 src/resources/fault
 libs
+*version.json
 
 # Node
 node_modules
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9e95147e13c34fac1b27c642c6ca90ff3b6c628e..de2734f5d5a8a3d009b81dca0feb9f2be06af132 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -151,8 +151,10 @@ Build Image:
   variables:
     DOCKER_BUILD_ARGS: |
       BUILD_IMAGE=${DEVOPS_REGISTRY}usgs/amazoncorretto:11
+      CI_COMMIT_BRANCH=${CI_COMMIT_BRANCH}
+      CI_JOB_TOKEN=${CI_JOB_TOKEN}
+      CI_PROJECT_URL=${CI_PROJECT_URL}
       FROM_IMAGE=${DEVOPS_REGISTRY}usgs/amazoncorretto:11
-      ci_job_token=${CI_JOB_TOKEN}
     UPSTREAM_PATH: ghsc/nshmp/nshmp-ws
 
 Build Project:
diff --git a/Dockerfile b/Dockerfile
index 1f24f62a7df7f183438af8d2fbf5750293618151..b7b5af9882540c05777e89c551a43a6cfb338776 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -22,13 +22,12 @@ ARG jar_file=${libs_dir}/${project}.jar
 FROM ${BUILD_IMAGE} as builder
 
 ARG builder_workdir
-ARG gitlab_token=null
-ARG ci_job_token=null
+ARG GITLAB_TOKEN=null
+ARG CI_JOB_TOKEN=null
 ARG jar_file
 ARG libs_dir
-
-ENV GITLAB_TOKEN ${gitlab_token}
-ENV CI_JOB_TOKEN ${ci_job_token}
+ARG CI_PROJECT_URL=null
+ARG CI_COMMIT_BRANCH=null
 
 WORKDIR ${builder_workdir}
 
diff --git a/build.gradle b/build.gradle
index 2fe8d6996ec15dc0e53f8623cdb8564904a97110..6b485bc54967592e2b2c27059761edf97656c56f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,6 +4,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}"
@@ -21,6 +22,7 @@ java {
   withSourcesJar()
 }
 
+apply from: "${projectDir}/gradle/app-version.gradle"
 apply from: "${projectDir}/gradle/dependencies.gradle"
 apply from: "${projectDir}/gradle/git-hooks.gradle"
 apply from: "${projectDir}/gradle/node.gradle"
diff --git a/gradle.properties b/gradle.properties
index 157f85c84fdcba3ec76925bbe26e5164b3678f5f..8bd1b109da073634c1aa132039f008f89d5d5453 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
 logbackVersion = 1.2.3
@@ -8,8 +9,8 @@ micronautPluginVersion = 3.1.1
 nodePluginVersion = 3.0.1
 nodeVersion = 16.3.0
 nshmFaultSectionsTag = v0.1
-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
diff --git a/gradle/app-version.gradle b/gradle/app-version.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..98f01e97a08b63eaac4334f11329617b67e9a7dc
--- /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-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/Utils.java b/src/main/java/gov/usgs/earthquake/nshmp/www/Utils.java
index 78fb37826888e60008306c639d9398b085cef4d4..9b39df8d2c0fc4690d22674a6887f3674cf26636 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/Utils.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/Utils.java
@@ -70,6 +70,7 @@ public class Utils {
     var svcResponse = ResponseBody.error()
         .name(name)
         .url(url)
+        .metadata(new ResponseMetadata(WsVersion.appVersions()))
         .request(msg)
         .response(url)
         .build();
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/WsVersion.java b/src/main/java/gov/usgs/earthquake/nshmp/www/WsVersion.java
new file mode 100644
index 0000000000000000000000000000000000000000..e5b7af1d716a06a4f527d8b20ef441b4ad02c018
--- /dev/null
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/WsVersion.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 WsVersion implements AppVersion {
+
+  public static VersionInfo[] appVersions() {
+    VersionInfo[] versions = {
+        new WsVersion().getVersionInfo(),
+        new LibVersion().getVersionInfo(),
+        new WsUtilsVersion().getVersionInfo(),
+    };
+    return versions;
+  }
+
+  public VersionInfo getVersionInfo() {
+    var resource = Resources.getResource("version/nshmp-ws-version.json");
+    return AppVersion.versionInfo(resource);
+  }
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GmmService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GmmService.java
index 8bff81f7f3ee2f46525924a37414e6bd647a1d0c..f9c9790405b19a92cb08ec184c3b08692a64b261 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GmmService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/GmmService.java
@@ -17,6 +17,8 @@ import gov.usgs.earthquake.nshmp.gmm.Gmm;
 import gov.usgs.earthquake.nshmp.gmm.GmmInput;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ResponseMetadata;
+import gov.usgs.earthquake.nshmp.www.WsVersion;
 import gov.usgs.earthquake.nshmp.www.gmm.GmmCalc.GmmData;
 import gov.usgs.earthquake.nshmp.www.gmm.XyDataGroup.EpiSeries;
 
@@ -119,6 +121,7 @@ class GmmService {
       var body = ResponseBody.success()
           .name(request.serviceId.name)
           .url(request.http.getUri().getPath())
+          .metadata(new ResponseMetadata(WsVersion.appVersions()))
           .request(request)
           .response(response)
           .build();
@@ -143,6 +146,7 @@ class GmmService {
       var body = ResponseBody.success()
           .name(request.serviceId.name)
           .url(request.http.getUri().getPath())
+          .metadata(new ResponseMetadata(WsVersion.appVersions()))
           .request(request)
           .response(response)
           .build();
@@ -199,6 +203,7 @@ class GmmService {
       var body = ResponseBody.success()
           .name(request.serviceId.name)
           .url(request.http.getUri().getPath())
+          .metadata(new ResponseMetadata(WsVersion.appVersions()))
           .request(request)
           .response(response)
           .build();
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/ServiceUtil.java b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/ServiceUtil.java
index f817cc48329d7d868655162e2f61647de90686ca..438f57490342bc22ca42805f031cb977079cafab 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/ServiceUtil.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/gmm/ServiceUtil.java
@@ -46,7 +46,9 @@ import gov.usgs.earthquake.nshmp.gmm.GmmInput.Constraints;
 import gov.usgs.earthquake.nshmp.gmm.GmmInput.Field;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ResponseMetadata;
 import gov.usgs.earthquake.nshmp.www.WsUtils;
+import gov.usgs.earthquake.nshmp.www.WsVersion;
 import gov.usgs.earthquake.nshmp.www.gmm.GmmService.Id;
 import gov.usgs.earthquake.nshmp.www.meta.EnumParameter;
 
@@ -132,6 +134,7 @@ class ServiceUtil {
     return ResponseBody.<String, MetadataResponse> usage()
         .name(service.name)
         .url(url)
+        .metadata(new ResponseMetadata(WsVersion.appVersions()))
         .request(url)
         .response(new MetadataResponse(request, service))
         .build();
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/FaultSectionsService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/FaultSectionsService.java
index bbe91f4ee9a886c18ebbaf3a6e31ef20d7805caf..2d4577d81b9fb33f3e2c804b0ec86e3d3a864ec4 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/FaultSectionsService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/FaultSectionsService.java
@@ -21,9 +21,11 @@ import gov.usgs.earthquake.nshmp.geo.json.GeoJson;
 import gov.usgs.earthquake.nshmp.internal.UsRegion;
 import gov.usgs.earthquake.nshmp.model.SourceFeature;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ResponseMetadata;
 import gov.usgs.earthquake.nshmp.www.Utils;
 import gov.usgs.earthquake.nshmp.www.Utils.Key;
 import gov.usgs.earthquake.nshmp.www.WsUtils;
+import gov.usgs.earthquake.nshmp.www.WsVersion;
 import gov.usgs.earthquake.nshmp.www.fault.FaultGroup;
 import gov.usgs.earthquake.nshmp.www.fault.FaultSections;
 import gov.usgs.earthquake.nshmp.www.fault.NshmFaultSection;
@@ -146,6 +148,7 @@ public class FaultSectionsService {
       var response = ResponseBody.success()
           .name(SERVICE_NAME)
           .url(request.getUri().getPath())
+          .metadata(new ResponseMetadata(WsVersion.appVersions()))
           .request(requestData)
           .response(builder.build().toJsonTree())
           .build();
@@ -171,6 +174,7 @@ public class FaultSectionsService {
       var response = ResponseBody.success()
           .name(SERVICE_NAME)
           .url(request.getUri().getPath())
+          .metadata(new ResponseMetadata(WsVersion.appVersions()))
           .request(requestData)
           .response(builder.build().toJsonTree())
           .build();
@@ -208,6 +212,7 @@ public class FaultSectionsService {
       var response = ResponseBody.success()
           .name(SERVICE_NAME)
           .url(request.getUri().getPath())
+          .metadata(new ResponseMetadata(WsVersion.appVersions()))
           .request(requestData)
           .response(builder.build().toJsonTree())
           .build();
@@ -285,6 +290,7 @@ public class FaultSectionsService {
     return ResponseBody.<String, MetadataResponse> usage()
         .name(SERVICE_NAME)
         .url(url)
+        .metadata(new ResponseMetadata(WsVersion.appVersions()))
         .request(url)
         .response(new MetadataResponse(request))
         .build();
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/GpsService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/GpsService.java
index 030355dcf4e199e730563cd2c87ed75b7da6f97a..5daff377a83f67459a306db3ea2e7a395eaeeb6e 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/GpsService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/GpsService.java
@@ -6,8 +6,10 @@ import static gov.usgs.earthquake.nshmp.www.WsUtils.checkValue;
 import java.util.EnumSet;
 
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ResponseMetadata;
 import gov.usgs.earthquake.nshmp.www.Utils;
 import gov.usgs.earthquake.nshmp.www.Utils.Key;
+import gov.usgs.earthquake.nshmp.www.WsVersion;
 import gov.usgs.earthquake.nshmp.www.gps.GpsDataSet;
 import gov.usgs.earthquake.nshmp.www.gps.GpsDataSets;
 import gov.usgs.earthquake.nshmp.www.meta.EnumParameter;
@@ -72,6 +74,7 @@ public class GpsService {
     var response = ResponseBody.success()
         .name(SERVICE_NAME)
         .url(request.getUri().getPath())
+        .metadata(new ResponseMetadata(WsVersion.appVersions()))
         .request(requestData)
         .response(values)
         .build();
@@ -121,6 +124,7 @@ public class GpsService {
     return ResponseBody.<String, MetadataResponse> usage()
         .name(SERVICE_NAME)
         .url(url)
+        .metadata(new ResponseMetadata(WsVersion.appVersions()))
         .request(url)
         .response(new MetadataResponse(request))
         .build();
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/GulfService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/GulfService.java
index e5b12ee50414f915fa1afba2dcfe8b72445a2a11..9bcddc786039b08276b35fb2caa15f6793d1840b 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/GulfService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/GulfService.java
@@ -8,8 +8,10 @@ import com.google.gson.JsonElement;
 import gov.usgs.earthquake.nshmp.geo.Location;
 import gov.usgs.earthquake.nshmp.geo.Region;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ResponseMetadata;
 import gov.usgs.earthquake.nshmp.www.Utils;
 import gov.usgs.earthquake.nshmp.www.Utils.Key;
+import gov.usgs.earthquake.nshmp.www.WsVersion;
 import gov.usgs.earthquake.nshmp.www.gulf.GulfData;
 import gov.usgs.earthquake.nshmp.www.gulf.GulfData.GulfDataResponse;
 
@@ -42,6 +44,7 @@ public class GulfService {
     return ResponseBody.<String, MetadataResponse> usage()
         .name(SERVICE_NAME)
         .url(url)
+        .metadata(new ResponseMetadata(WsVersion.appVersions()))
         .request(url)
         .response(new MetadataResponse(request))
         .build();
@@ -91,6 +94,7 @@ public class GulfService {
     return ResponseBody.<RequestData, JsonElement> success()
         .name(SERVICE_NAME)
         .url(request.getUri().getPath())
+        .metadata(new ResponseMetadata(WsVersion.appVersions()))
         .request(requestData)
         .response(value)
         .build();