Index: TFP-WebServer/WebServer/src/WebAPI/OpenApiHelpers.cs
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/OpenApiHelpers.cs	(revision 460)
+++ TFP-WebServer/WebServer/src/WebAPI/OpenApiHelpers.cs	(revision 463)
@@ -4,4 +4,5 @@
 using System.Text;
 using System.Text.RegularExpressions;
+using Webserver.UrlHandlers;
 
 namespace Webserver.WebAPI {
@@ -10,12 +11,9 @@
 		private const string masterDocName = "openapi.yaml";
 
-		private const string masterDocRefLineBegin = "    - $ref: ";
-		private const string masterDocRefLineEnd = "#/paths";
-
 		private struct OpenApiSpec {
-			public readonly List<string> ExportedPaths;
+			public readonly Dictionary<string, string> ExportedPaths;
 			public readonly string Spec;
 
-			public OpenApiSpec (string _spec, List<string> _exportedPaths = null) {
+			public OpenApiSpec (string _spec, Dictionary<string, string> _exportedPaths = null) {
 				ExportedPaths = _exportedPaths;
 				Spec = _spec;
@@ -57,10 +55,10 @@
 				}
 
-				if ((spec.ExportedPaths?.Count ?? 0) < 1) {
+				if (spec.ExportedPaths == null || spec.ExportedPaths.Count < 1) {
 					continue;
 				}
 
-				foreach (string exportedPath in spec.ExportedPaths) {
-					writePath (sb, apiSpecName, exportedPath);
+				foreach ((string exportedPath, string rebasedPath) in spec.ExportedPaths) {
+					writePath (sb, apiSpecName, exportedPath, rebasedPath);
 				}
 			}
@@ -71,128 +69,9 @@
 		}
 
-		private void writePath (StringBuilder _sb, string _apiSpecName, string _exportedPath) {
-			_sb.AppendLine ($"  {_exportedPath}:");
+		private void writePath (StringBuilder _sb, string _apiSpecName, string _exportedPath, string _rebasedPath) {
+			_sb.AppendLine ($"  {_rebasedPath ?? _exportedPath}:");
 			_sb.Append ($"    $ref: './{_apiSpecName}#/paths/");
 
-			for (int i = 0; i < _exportedPath.Length; i++) {
-				char c = _exportedPath[i];
-
-				switch (c) {
-					// JSON string escaped characters
-					case '"':
-						_sb.Append ("\\\"");
-						break;
-					case '\\':
-						_sb.Append ("\\\\");
-						break;
-					case '\b':
-						_sb.Append ("\\b");
-						break;
-					case '\f':
-						_sb.Append ("\\f");
-						break;
-					case '\n':
-						_sb.Append ("\\n");
-						break;
-					case '\r':
-						_sb.Append ("\\r");
-						break;
-					case '\t':
-						_sb.Append ("\\t");
-						break;
-					case (char)0x00:
-						_sb.Append ("\\u0000");
-						break;
-					case (char)0x01:
-						_sb.Append ("\\u0001");
-						break;
-					case (char)0x02:
-						_sb.Append ("\\u0002");
-						break;
-					case (char)0x03:
-						_sb.Append ("\\u0003");
-						break;
-					case (char)0x04:
-						_sb.Append ("\\u0004");
-						break;
-					case (char)0x05:
-						_sb.Append ("\\u0005");
-						break;
-					case (char)0x06:
-						_sb.Append ("\\u0006");
-						break;
-					case (char)0x07:
-						_sb.Append ("\\u0007");
-						break;
-					case (char)0x0b:
-						_sb.Append ("\\u000b");
-						break;
-					case (char)0x0e:
-						_sb.Append ("\\u000e");
-						break;
-					case (char)0x0f:
-						_sb.Append ("\\u000f");
-						break;
-					case (char)0x10:
-						_sb.Append ("\\u0010");
-						break;
-					case (char)0x11:
-						_sb.Append ("\\u0011");
-						break;
-					case (char)0x12:
-						_sb.Append ("\\u0012");
-						break;
-					case (char)0x13:
-						_sb.Append ("\\u0013");
-						break;
-					case (char)0x14:
-						_sb.Append ("\\u0014");
-						break;
-					case (char)0x15:
-						_sb.Append ("\\u0015");
-						break;
-					case (char)0x16:
-						_sb.Append ("\\u0016");
-						break;
-					case (char)0x17:
-						_sb.Append ("\\u0017");
-						break;
-					case (char)0x18:
-						_sb.Append ("\\u0018");
-						break;
-					case (char)0x19:
-						_sb.Append ("\\u0019");
-						break;
-					case (char)0x1a:
-						_sb.Append ("\\u001a");
-						break;
-					case (char)0x1b:
-						_sb.Append ("\\u001b");
-						break;
-					case (char)0x1c:
-						_sb.Append ("\\u001c");
-						break;
-					case (char)0x1d:
-						_sb.Append ("\\u001d");
-						break;
-					case (char)0x1e:
-						_sb.Append ("\\u001e");
-						break;
-					case (char)0x1f:
-						_sb.Append ("\\u001f");
-						break;
-					// JSON Pointer specific
-					case '/':
-						_sb.Append ("~1");
-						break;
-					case '~':
-						_sb.Append ("~0");
-						break;
-					// Non escaped characters
-					default:
-						_sb.Append (c);
-						break;
-				}
-			}
+			writeJsonPointerEncodedPath (_sb, _exportedPath);
 			
 			_sb.AppendLine ("'");
@@ -214,7 +93,35 @@
 		}
 
+		public void LoadOpenApiSpec (AbsHandler _pathHandler) {
+			Assembly apiAssembly = _pathHandler.GetType ().Assembly;
+			string apiName = _pathHandler.GetType ().Name;
+			string apiSpecName = $"{apiName}.openapi.yaml";
+
+			string specText = ResourceHelpers.GetManifestResourceText (apiAssembly, apiSpecName, true);
+			if (specText == null) {
+				return;
+			}
+
+			Log.Out ($"[Web] Loaded OpenAPI spec for '{apiName}'");
+			OpenApiSpec spec = new OpenApiSpec (specText, findExportedPaths (specText, _pathHandler.UrlBasePath));
+			specs.Add (apiSpecName, spec);
+		}
+
+		public void RegisterCustomSpec (Assembly _assembly, string _apiSpecName, string _replaceBasePath = null) {
+			string apiSpecName = $"{_apiSpecName}.openapi.yaml";
+
+			string specText = ResourceHelpers.GetManifestResourceText (_assembly, apiSpecName, true);
+			if (specText == null) {
+				return;
+			}
+
+			Log.Out ($"[Web] Loaded OpenAPI spec for '{_apiSpecName}'");
+			OpenApiSpec spec = new OpenApiSpec (specText, findExportedPaths (specText, _replaceBasePath));
+			specs.Add (apiSpecName, spec);
+		}
+
 		private static readonly Regex pathMatcher = new Regex ("^\\s{1,2}(/\\S+):.*$", RegexOptions.Compiled | RegexOptions.CultureInvariant);
-		private List<string> findExportedPaths (string _spec) {
-			List<string> result = new List<string> ();
+		private Dictionary<string, string> findExportedPaths (string _spec, string _replaceBasePath = null) {
+			Dictionary<string, string> result = new Dictionary<string, string> ();
 
 			using TextReader tr = new StringReader (_spec);
@@ -234,6 +141,10 @@
 
 					string path = match.Groups [1].Value;
+					string rebasedPath = null;
 					Log.Out ($"[Web]   Exports: {path}");
-					result.Add (path);
+					if (_replaceBasePath != null) {
+						rebasedPath = path.Replace ("/BASEPATH/", _replaceBasePath);
+					}
+					result [path] = rebasedPath;
 				}
 			}
@@ -255,4 +166,128 @@
 			return true;
 		}
+
+		private void writeJsonPointerEncodedPath (StringBuilder _targetSb, string _path) {
+			for (int i = 0; i < _path.Length; i++) {
+				char c = _path[i];
+
+				switch (c) {
+					// JSON string escaped characters
+					case '"':
+						_targetSb.Append ("\\\"");
+						break;
+					case '\\':
+						_targetSb.Append ("\\\\");
+						break;
+					case '\b':
+						_targetSb.Append ("\\b");
+						break;
+					case '\f':
+						_targetSb.Append ("\\f");
+						break;
+					case '\n':
+						_targetSb.Append ("\\n");
+						break;
+					case '\r':
+						_targetSb.Append ("\\r");
+						break;
+					case '\t':
+						_targetSb.Append ("\\t");
+						break;
+					case (char)0x00:
+						_targetSb.Append ("\\u0000");
+						break;
+					case (char)0x01:
+						_targetSb.Append ("\\u0001");
+						break;
+					case (char)0x02:
+						_targetSb.Append ("\\u0002");
+						break;
+					case (char)0x03:
+						_targetSb.Append ("\\u0003");
+						break;
+					case (char)0x04:
+						_targetSb.Append ("\\u0004");
+						break;
+					case (char)0x05:
+						_targetSb.Append ("\\u0005");
+						break;
+					case (char)0x06:
+						_targetSb.Append ("\\u0006");
+						break;
+					case (char)0x07:
+						_targetSb.Append ("\\u0007");
+						break;
+					case (char)0x0b:
+						_targetSb.Append ("\\u000b");
+						break;
+					case (char)0x0e:
+						_targetSb.Append ("\\u000e");
+						break;
+					case (char)0x0f:
+						_targetSb.Append ("\\u000f");
+						break;
+					case (char)0x10:
+						_targetSb.Append ("\\u0010");
+						break;
+					case (char)0x11:
+						_targetSb.Append ("\\u0011");
+						break;
+					case (char)0x12:
+						_targetSb.Append ("\\u0012");
+						break;
+					case (char)0x13:
+						_targetSb.Append ("\\u0013");
+						break;
+					case (char)0x14:
+						_targetSb.Append ("\\u0014");
+						break;
+					case (char)0x15:
+						_targetSb.Append ("\\u0015");
+						break;
+					case (char)0x16:
+						_targetSb.Append ("\\u0016");
+						break;
+					case (char)0x17:
+						_targetSb.Append ("\\u0017");
+						break;
+					case (char)0x18:
+						_targetSb.Append ("\\u0018");
+						break;
+					case (char)0x19:
+						_targetSb.Append ("\\u0019");
+						break;
+					case (char)0x1a:
+						_targetSb.Append ("\\u001a");
+						break;
+					case (char)0x1b:
+						_targetSb.Append ("\\u001b");
+						break;
+					case (char)0x1c:
+						_targetSb.Append ("\\u001c");
+						break;
+					case (char)0x1d:
+						_targetSb.Append ("\\u001d");
+						break;
+					case (char)0x1e:
+						_targetSb.Append ("\\u001e");
+						break;
+					case (char)0x1f:
+						_targetSb.Append ("\\u001f");
+						break;
+					// JSON Pointer specific
+					case '/':
+						_targetSb.Append ("~1");
+						break;
+					case '~':
+						_targetSb.Append ("~0");
+						break;
+					// Non escaped characters
+					default:
+						_targetSb.Append (c);
+						break;
+				}
+			}
+		}
+
 	}
 }
