diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts
index a9efc034..f64b5680 100644
--- a/src/libs/endorserServer.ts
+++ b/src/libs/endorserServer.ts
@@ -1,3 +1,21 @@
+/**
+ * @fileoverview Endorser Server Interface and Utilities
+ * @author Matthew Raymer
+ * 
+ * This module provides the interface and utilities for interacting with the Endorser server.
+ * It handles authentication, data validation, and server communication for claims, contacts,
+ * and other core functionality.
+ * 
+ * Key Features:
+ * - Deep link URL path constants
+ * - DID validation and handling
+ * - Contact management utilities
+ * - Server authentication
+ * - Plan caching
+ * 
+ * @module endorserServer
+ */
+
 import { Axios, AxiosRequestConfig } from "axios";
 import { Buffer } from "buffer";
 import { sha256 } from "ethereum-cryptography/sha256";
@@ -31,21 +49,48 @@ import {
   CreateAndSubmitClaimResult,
 } from "../interfaces";
 
+/**
+ * Standard context for schema.org data
+ * @constant {string}
+ */
 export const SCHEMA_ORG_CONTEXT = "https://schema.org";
-// the object in RegisterAction claims
+
+/**
+ * Service identifier for RegisterAction claims
+ * @constant {string}
+ */
 export const SERVICE_ID = "endorser.ch";
-// the header line for contacts exported via Endorser Mobile
+
+/**
+ * Header line format for contacts exported via Endorser Mobile
+ * @constant {string}
+ */
 export const CONTACT_CSV_HEADER = "name,did,pubKeyBase64,seesMe,registered";
-// the suffix for the contact URL in this app where they are confirmed before import
+
+/**
+ * URL path suffix for contact confirmation before import
+ * @constant {string}
+ */
 export const CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI = "/contact-import/";
-// the suffix for the contact URL in this app where a single one gets imported automatically
+
+/**
+ * URL path suffix for the contact URL in this app where a single one gets imported automatically
+ * @constant {string}
+ */
 export const CONTACT_IMPORT_ONE_URL_PATH_TIME_SAFARI = "/contacts?contactJwt=";
-// the suffix for the old contact URL -- deprecated Jan 2025, though "endorser.ch/contact?jwt=" shows data on endorser.ch server
+
+/**
+ * URL path suffix for the old contact URL -- deprecated Jan 2025, though "endorser.ch/contact?jwt=" shows data on endorser.ch server
+ * @constant {string}
+ */
 export const CONTACT_URL_PATH_ENDORSER_CH_OLD = "/contact?jwt=";
-// unused now that we match on the URL path; just note that it was used for a while to create URLs that showed at endorser.ch
-//export const CONTACT_URL_PREFIX_ENDORSER_CH_OLD = "https://endorser.ch";
-// the prefix for handle IDs, the permanent ID for claims on Endorser
+
+/**
+ * The prefix for handle IDs, the permanent ID for claims on Endorser
+ * @constant {string}
+ */
 export const ENDORSER_CH_HANDLE_PREFIX = "https://endorser.ch/entity/";
+
 export const BLANK_GENERIC_SERVER_RECORD: GenericCredWrapper<GenericVerifiableCredential> =
   {
     claim: { "@type": "" },
@@ -54,41 +99,99 @@ export const BLANK_GENERIC_SERVER_RECORD: GenericCredWrapper<GenericVerifiableCr
     issuedAt: "",
     issuer: "",
   };
+
 // This is used to check for hidden info.
 // See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6
 const HIDDEN_DID = "did:none:HIDDEN";
 
-export function isDid(did: string) {
+/**
+ * Validates if a string is a valid DID
+ * @param {string} did - The DID string to validate
+ * @returns {boolean} True if string is a valid DID format
+ */
+export function isDid(did: string): boolean {
   return did.startsWith("did:");
 }
 
-export function isHiddenDid(did: string) {
+/**
+ * Checks if a DID is the special hidden DID value
+ * @param {string} did - The DID to check
+ * @returns {boolean} True if DID is hidden
+ */
+export function isHiddenDid(did: string): boolean {
   return did === HIDDEN_DID;
 }
 
-export function isEmptyOrHiddenDid(did?: string) {
-  return !did || did === HIDDEN_DID; // catching empty string as well
+/**
+ * Checks if a DID is empty or hidden
+ * @param {string} [did] - The DID to check
+ * @returns {boolean} True if DID is empty or hidden
+ */
+export function isEmptyOrHiddenDid(did?: string): boolean {
+  return !did || did === HIDDEN_DID;
 }
 
 /**
- * @return true for any string within this primitive/object/array where func(input) === true
- *
- * Similar logic is found in endorser-mobile.
+ * Recursively tests strings within an object/array against a test function
+ * @param {Function} func - Test function to apply to strings
+ * @param {any} input - Object/array to recursively test
+ * @returns {boolean} True if any string passes the test function
+ * 
+ * @example
+ * testRecursivelyOnStrings(isDid, { user: { id: "did:example:123" } })
+ * // Returns: true
  */
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function testRecursivelyOnStrings(func: (arg0: any) => boolean, input: any) {
+/**
+ * Recursively tests strings within a nested object/array structure against a test function
+ * 
+ * This function traverses through objects and arrays to find all string values and applies
+ * a test function to each string found. It handles:
+ * - Direct string values
+ * - Strings in objects (at any depth)
+ * - Strings in arrays (at any depth)
+ * - Mixed nested structures (objects containing arrays containing objects, etc)
+ * 
+ * @param {Function} func - Test function that takes a string and returns boolean
+ * @param {any} input - Value to recursively search (can be string, object, array, or other)
+ * @returns {boolean} True if any string in the structure passes the test function
+ * 
+ * @example
+ * // Test if any string is a DID
+ * const obj = {
+ *   user: { 
+ *     id: "did:example:123",
+ *     details: ["name", "did:example:456"] 
+ *   }
+ * };
+ * testRecursivelyOnStrings(isDid, obj); // Returns: true
+ * 
+ * @example
+ * // Test for hidden DIDs
+ * const obj = {
+ *   visible: "did:example:123",
+ *   hidden: ["did:none:HIDDEN"]
+ * };
+ * testRecursivelyOnStrings(isHiddenDid, obj); // Returns: true
+ */
+function testRecursivelyOnStrings(
+  func: (arg0: any) => boolean,
+  input: any
+): boolean {
+  // Test direct string values
   if (Object.prototype.toString.call(input) === "[object String]") {
     return func(input);
-  } else if (input instanceof Object) {
+  } 
+  // Recursively test objects and arrays
+  else if (input instanceof Object) {
     if (!Array.isArray(input)) {
-      // it's an object
+      // Handle plain objects
       for (const key in input) {
         if (testRecursivelyOnStrings(func, input[key])) {
           return true;
         }
       }
     } else {
-      // it's an array
+      // Handle arrays
       for (const value of input) {
         if (testRecursivelyOnStrings(func, value)) {
           return true;
@@ -97,6 +200,7 @@ function testRecursivelyOnStrings(func: (arg0: any) => boolean, input: any) {
     }
     return false;
   } else {
+    // Non-string, non-object values can't contain strings
     return false;
   }
 }
@@ -363,15 +467,23 @@ export async function getHeaders(
   return headers;
 }
 
+/**
+ * Cache for storing plan data
+ * @constant {LRUCache}
+ */
 const planCache: LRUCache<string, PlanSummaryRecord> = new LRUCache({
   max: 500,
 });
 
 /**
- * @param handleId nullable, in which case "undefined" will be returned
- * @param requesterDid optional, in which case no private info will be returned
- * @param axios
- * @param apiServer
+ * Retrieves plan data from cache or server
+ * @param {string} handleId - Plan handle ID
+ * @param {Axios} axios - Axios instance
+ * @param {string} apiServer - API server URL
+ * @param {string} [requesterDid] - Optional requester DID for private info
+ * @returns {Promise<PlanSummaryRecord|undefined>} Plan data or undefined if not found
+ * 
+ * @throws {Error} If server request fails
  */
 export async function getPlanFromCache(
   handleId: string | undefined,
@@ -414,25 +526,25 @@ export async function getPlanFromCache(
   return cred;
 }
 
+/**
+ * Updates plan data in cache
+ * @param {string} handleId - Plan handle ID
+ * @param {PlanSummaryRecord} planSummary - Plan data to cache
+ */
 export async function setPlanInCache(
   handleId: string,
   planSummary: PlanSummaryRecord,
-) {
+): Promise<void> {
   planCache.set(handleId, planSummary);
 }
 
 /**
- *
- * @param error that is thrown from an Endorser server call by Axios
- * @returns user-friendly message, or undefined if none found
+ * Extracts user-friendly message from server error
+ * @param {any} error - Error thrown from Endorser server call
+ * @returns {string|undefined} User-friendly message or undefined if none found
  */
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export function serverMessageForUser(error: any) {
-  return (
-    // this is how most user messages are returned
-    error?.response?.data?.error?.message
-    // some are returned as "error" with a string, but those are more for devs and are less helpful to the user
-  );
+export function serverMessageForUser(error: any): string | undefined {
+  return error?.response?.data?.error?.message;
 }
 
 /**
diff --git a/test-deeplinks.sh b/test-deeplinks.sh
new file mode 100644
index 00000000..a10186bb
--- /dev/null
+++ b/test-deeplinks.sh
@@ -0,0 +1,89 @@
+#!/bin/bash
+
+# Configurable pause duration (in seconds)
+PAUSE_DURATION=2
+MANUAL_CONTINUE=true
+
+# Function to test deep link
+test_link() {
+    echo "----------------------------------------"
+    echo "Testing: $1"
+    echo "----------------------------------------"
+    adb shell am start -W -a android.intent.action.VIEW -d "$1" app.timesafari.app
+    
+    if [ "$MANUAL_CONTINUE" = true ]; then
+        read -p "Press Enter to continue to next test..."
+    else
+        sleep $PAUSE_DURATION
+    fi
+}
+
+# Allow command line override of pause settings
+while getopts "t:a" opt; do
+  case $opt in
+    t) PAUSE_DURATION=$OPTARG ;;
+    a) MANUAL_CONTINUE=false ;;
+  esac
+done
+
+echo "Starting TimeSafari Deep Link Tests"
+echo "======================================"
+echo "Pause duration: $PAUSE_DURATION seconds"
+echo "Manual continue: $MANUAL_CONTINUE"
+
+# Test claim routes
+echo "\nTesting Claim Routes:"
+test_link "timesafari://claim/01JMAAFZRNSRTQ0EBSD70A8E1H"
+test_link "timesafari://claim/01JMAAFZRNSRTQ0EBSD70A8E1H?view=details"
+test_link "timesafari://claim-cert/01JMAAFZRNSRTQ0EBSD70A8E1H"
+
+# Test contact routes
+echo "\nTesting Contact Routes:"
+test_link "timesafari://contact-import/eyJhbGciOiJFUzI1NksifQ"
+test_link "timesafari://contact-edit/did:example:123"
+
+# Test project routes
+echo "\nTesting Project Routes:"
+test_link "timesafari://project/456?view=details"
+
+# Test invite routes
+echo "\nTesting Invite Routes:"
+test_link "timesafari://invite-one-accept/eyJhbGciOiJFUzI1NksifQ"
+
+# Test gift routes
+echo "\nTesting Gift Routes:"
+test_link "timesafari://confirm-gift/789"
+
+# Test offer routes
+echo "\nTesting Offer Routes:"
+test_link "timesafari://offer-details/101"
+
+# Test complex query parameters
+echo "\nTesting Complex Query Parameters:"
+test_link "timesafari://contact-import/jwt?contacts=%5B%7B%22name%22%3A%22Test%22%7D%5D"
+
+# New test cases
+echo "\nTesting DID Routes:"
+test_link "timesafari://did/did:example:123"
+test_link "timesafari://did/did:example:456?view=details"
+
+echo "\nTesting Additional Claim Routes:"
+test_link "timesafari://claim/123?view=certificate"
+test_link "timesafari://claim/123?view=raw"
+test_link "timesafari://claim-add-raw/123?claimJwtId=jwt123"
+
+echo "\nTesting Additional Contact Routes:"
+test_link "timesafari://contact-import/jwt?contacts=%5B%7B%22did%22%3A%22did%3Aexample%3A123%22%7D%5D"
+test_link "timesafari://contact-edit/did:example:123?action=edit"
+
+echo "\nTesting Error Cases:"
+test_link "timesafari://invalid-route/123"
+test_link "timesafari://claim/123?view=invalid"
+test_link "timesafari://did/invalid-did"
+
+# Test contact import one route
+echo "\nTesting Contact Import One Route:"
+test_link "timesafari://contacts?contactJwt=eyJhbGciOiJFUzI1NksifQ"
+
+echo "\nDeep link testing complete"
+echo "======================================" 
\ No newline at end of file