Index: TFP-WebServer/WebServer/src/WebAPI/APIs/Command.openapi.yaml
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/APIs/Command.openapi.yaml	(revision 462)
+++ TFP-WebServer/WebServer/src/WebAPI/APIs/Command.openapi.yaml	(revision 463)
@@ -195,4 +195,5 @@
           $ref: './openapi.yaml#/components/responses/Unauthorized'
       security:
+        - {}
         - apiTokenName: []
           apiTokenSecret: []
@@ -215,2 +216,7 @@
           description: Command not found
           $ref: './openapi.yaml#/components/responses/HttpEmptyEnvelopedResponse'
+      security:
+        - {}
+        - apiTokenName: [ ]
+          apiTokenSecret: [ ]
+        - sessionCookie: [ ]
Index: TFP-WebServer/WebServer/src/WebAPI/APIs/ServerState/GamePrefs.openapi.yaml
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/APIs/ServerState/GamePrefs.openapi.yaml	(revision 463)
+++ TFP-WebServer/WebServer/src/WebAPI/APIs/ServerState/GamePrefs.openapi.yaml	(revision 463)
@@ -0,0 +1,144 @@
+openapi: 3.1.0
+info:
+  title: GamePrefs
+  version: '1'
+
+components:
+  schemas:
+    GamePrefElementInt:
+      type: object
+      properties:
+        name:
+          type: string
+          examples:
+            - WebDashboardPort
+        type:
+          type: string
+          const: 'int'
+        value:
+          type: integer
+          examples:
+            - 8000
+        default:
+          type: integer
+          examples:
+            - 8080
+      required:
+        - name
+        - type
+        - value
+        - default
+
+    GamePrefElementFloat:
+      type: object
+      properties:
+        name:
+          type: string
+          examples:
+            - SaveDataLimit
+        type:
+          type: string
+          const: 'float'
+        value:
+          type: number
+          format: float
+          examples:
+            - 150
+        default:
+          type: number
+          format: float
+          examples:
+            - -1
+      required:
+        - name
+        - type
+        - value
+        - default
+
+    GamePrefElementBool:
+      type: object
+      properties:
+        name:
+          type: string
+          examples:
+            - EnableMapRendering
+        type:
+          type: string
+          const: 'bool'
+        value:
+          type: boolean
+          examples:
+            - true
+        default:
+          type: boolean
+          examples:
+            - false
+      required:
+        - name
+        - type
+        - value
+        - default
+
+    GamePrefElementString:
+      type: object
+      properties:
+        name:
+          type: string
+          examples:
+            - GameWorld
+        type:
+          type: string
+          const: 'string'
+        value:
+          type: string
+          examples:
+            - Navezgane
+        default:
+          type: string
+          examples:
+            - ''
+      required:
+        - name
+        - type
+        - value
+        - default
+
+    GamePrefList:
+      type: array
+      items:
+        anyOf:
+          - $ref: '#/components/schemas/GamePrefElementInt'
+          - $ref: '#/components/schemas/GamePrefElementFloat'
+          - $ref: '#/components/schemas/GamePrefElementBool'
+          - $ref: '#/components/schemas/GamePrefElementString'
+
+
+paths:
+  /api/gameprefs:
+    get:
+      tags:
+        - ServerState
+      summary: GamePrefs list
+      description: Fetch a list of GamePrefs
+      operationId: GamePrefs.get
+      responses:
+        200:
+          description: List of GamePrefs
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  data:
+                    $ref: '#/components/schemas/GamePrefList'
+                  meta:
+                    $ref: './openapi.yaml#/components/schemas/ResultEnvelopeMeta'
+                required:
+                  - data
+                  - meta
+        403:
+          $ref: './openapi.yaml#/components/responses/Unauthorized'
+      security:
+        - apiTokenName: [ ]
+          apiTokenSecret: [ ]
+        - sessionCookie: [ ]
Index: TFP-WebServer/WebServer/src/WebAPI/APIs/ServerState/GameStats.openapi.yaml
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/APIs/ServerState/GameStats.openapi.yaml	(revision 463)
+++ TFP-WebServer/WebServer/src/WebAPI/APIs/ServerState/GameStats.openapi.yaml	(revision 463)
@@ -0,0 +1,123 @@
+openapi: 3.1.0
+info:
+  title: GameStats
+  version: '1'
+
+components:
+  schemas:
+    GameStatElementInt:
+      type: object
+      properties:
+        name:
+          type: string
+          examples:
+            - TimeOfDayIncPerSec
+        type:
+          type: string
+          const: 'int'
+        value:
+          type: integer
+          examples:
+            - 6
+      required:
+        - name
+        - type
+        - value
+
+    GameStatElementFloat:
+      type: object
+      properties:
+        name:
+          type: string
+          examples:
+            - GameDifficultyBonus
+        type:
+          type: string
+          const: 'float'
+        value:
+          type: number
+          format: float
+          examples:
+            - 1
+      required:
+        - name
+        - type
+        - value
+
+    GameStatElementBool:
+      type: object
+      properties:
+        name:
+          type: string
+          examples:
+            - IsSpawnEnemies
+        type:
+          type: string
+          const: 'bool'
+        value:
+          type: boolean
+          examples:
+            - true
+      required:
+        - name
+        - type
+        - value
+
+    GameStatElementString:
+      type: object
+      properties:
+        name:
+          type: string
+          examples:
+            - GameWorld
+        type:
+          type: string
+          const: 'string'
+        value:
+          type: string
+          examples:
+            - Navezgane
+      required:
+        - name
+        - type
+        - value
+
+    GameStatList:
+      type: array
+      items:
+        anyOf:
+          - $ref: '#/components/schemas/GameStatElementInt'
+          - $ref: '#/components/schemas/GameStatElementFloat'
+          - $ref: '#/components/schemas/GameStatElementBool'
+          - $ref: '#/components/schemas/GameStatElementString'
+
+
+paths:
+  /api/gamestats:
+    get:
+      tags:
+        - ServerState
+      summary: GameStats list
+      description: Fetch a list of GameStats
+      operationId: GameStats.get
+      responses:
+        200:
+          description: List of GameStats
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  data:
+                    $ref: '#/components/schemas/GameStatList'
+                  meta:
+                    $ref: './openapi.yaml#/components/schemas/ResultEnvelopeMeta'
+                required:
+                  - data
+                  - meta
+        403:
+          $ref: './openapi.yaml#/components/responses/Unauthorized'
+      security:
+        - apiTokenName: [ ]
+          apiTokenSecret: [ ]
+        - sessionCookie: [ ]
Index: TFP-WebServer/WebServer/src/WebAPI/APIs/ServerState/ServerInfo.openapi.yaml
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/APIs/ServerState/ServerInfo.openapi.yaml	(revision 463)
+++ TFP-WebServer/WebServer/src/WebAPI/APIs/ServerState/ServerInfo.openapi.yaml	(revision 463)
@@ -0,0 +1,117 @@
+openapi: 3.1.0
+info:
+  title: ServerInfo
+  version: '1'
+
+components:
+  schemas:
+    ServerInfoElementInt:
+      type: object
+      properties:
+        name:
+          type: string
+          examples:
+            - TimeOfDayIncPerSec
+        type:
+          type: string
+          const: 'int'
+        value:
+          type: integer
+          examples:
+            - 6
+      required:
+        - name
+        - type
+        - value
+
+    ServerInfoElementFloat:
+      type: object
+      properties:
+        name:
+          type: string
+          examples:
+            - GameDifficultyBonus
+        type:
+          type: string
+          const: 'float'
+        value:
+          type: number
+          format: float
+          examples:
+            - 1
+      required:
+        - name
+        - type
+        - value
+
+    ServerInfoElementBool:
+      type: object
+      properties:
+        name:
+          type: string
+          examples:
+            - IsSpawnEnemies
+        type:
+          type: string
+          const: 'bool'
+        value:
+          type: boolean
+          examples:
+            - true
+      required:
+        - name
+        - type
+        - value
+
+    ServerInfoElementString:
+      type: object
+      properties:
+        name:
+          type: string
+          examples:
+            - GameWorld
+        type:
+          type: string
+          const: 'string'
+        value:
+          type: string
+          examples:
+            - Navezgane
+      required:
+        - name
+        - type
+        - value
+
+    ServerInfoList:
+      type: array
+      items:
+        anyOf:
+          - $ref: '#/components/schemas/ServerInfoElementInt'
+          - $ref: '#/components/schemas/ServerInfoElementFloat'
+          - $ref: '#/components/schemas/ServerInfoElementBool'
+          - $ref: '#/components/schemas/ServerInfoElementString'
+
+
+paths:
+  /api/serverinfo:
+    get:
+      tags:
+        - ServerState
+      summary: ServerInfo
+      description: Fetch the ServerInfo
+      operationId: ServerInfo.get
+      responses:
+        200:
+          description: List of ServerInfo data
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  data:
+                    $ref: '#/components/schemas/ServerInfoList'
+                  meta:
+                    $ref: './openapi.yaml#/components/schemas/ResultEnvelopeMeta'
+                required:
+                  - data
+                  - meta
Index: TFP-WebServer/WebServer/src/WebAPI/APIs/ServerState/ServerStats.openapi.yaml
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/APIs/ServerState/ServerStats.openapi.yaml	(revision 463)
+++ TFP-WebServer/WebServer/src/WebAPI/APIs/ServerState/ServerStats.openapi.yaml	(revision 463)
@@ -0,0 +1,52 @@
+openapi: 3.1.0
+info:
+  title: ServerStats
+  version: '1'
+
+components:
+  schemas:
+    ServerStatsObject:
+      type: object
+      properties: 
+        gameTime:
+          $ref: './openapi.yaml#/components/schemas/TypeGameTimeObject'
+          description: Current in-game time
+        players:
+          type: integer
+          description: Currently logged in players
+        hostiles:
+          type: integer
+          description: Currently active hostiles, including hostile animals
+        animals:
+          type: integer
+          description: Currently active (non-hostile) animals
+      required:
+        - gameTime
+        - players
+        - hostiles
+        - animals
+
+
+paths:
+  /api/serverstats:
+    get:
+      tags:
+        - ServerState
+      summary: ServerStats
+      description: Fetch the current server stats
+      operationId: ServerStats.get
+      responses:
+        200:
+          description: Server stats
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  data:
+                    $ref: '#/components/schemas/ServerStatsObject'
+                  meta:
+                    $ref: './openapi.yaml#/components/schemas/ResultEnvelopeMeta'
+                required:
+                  - data
+                  - meta
Index: TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Animal.openapi.yaml
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Animal.openapi.yaml	(revision 462)
+++ TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Animal.openapi.yaml	(revision 463)
@@ -34,5 +34,5 @@
         - WorldState
       summary: Animals list
-      description: Fetch a list of the currently spawned animals in the world
+      description: Fetch a list of the currently spawned (non-hostile) animals in the world
       operationId: animal.get
       responses:
Index: TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Bloodmoon.openapi.yaml
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Bloodmoon.openapi.yaml	(revision 463)
+++ TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Bloodmoon.openapi.yaml	(revision 463)
@@ -0,0 +1,58 @@
+openapi: 3.1.0
+info:
+  title: Bloodmoon
+  version: '1'
+
+components:
+  schemas:
+    BloodmoonObject:
+      type: object
+      properties:
+        gameTime:
+          $ref: './openapi.yaml#/components/schemas/TypeGameTimeObject'
+          description: Current in-game time
+        bloodmoonActive:
+          type: boolean
+          description: A bloodmoon is currently happening
+        nextBloodmoon:
+          $ref: './openapi.yaml#/components/schemas/TypeGameTimeObject'
+          description: In-game time of start of next bloodmoon or the currently running one
+        nextBloodmoonEnd:
+          $ref: './openapi.yaml#/components/schemas/TypeGameTimeObject'
+          description: In-game time of the end of the next bloodmoon - or end of the current one if running
+      required:
+        - gameTime
+        - bloodmoonActive
+        - nextBloodmoon
+        - nextBloodmoonEnd
+
+
+paths:
+  /api/bloodmoon:
+    get:
+      tags:
+        - WorldState
+      summary: Bloodmoon
+      description: Get info on the bloodmoon
+      operationId: Bloodmoon.get
+      responses:
+        200:
+          description: Bloodmoon info
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  data:
+                    $ref: '#/components/schemas/BloodmoonObject'
+                  meta:
+                    $ref: './openapi.yaml#/components/schemas/ResultEnvelopeMeta'
+                required:
+                  - data
+                  - meta
+        403:
+          $ref: './openapi.yaml#/components/responses/Unauthorized'
+      security:
+        - apiTokenName: [ ]
+          apiTokenSecret: [ ]
+        - sessionCookie: [ ]
Index: TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Hostile.openapi.yaml
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Hostile.openapi.yaml	(revision 463)
+++ TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Hostile.openapi.yaml	(revision 463)
@@ -0,0 +1,58 @@
+openapi: 3.1.0
+info:
+  title: Hostile
+  version: '1'
+
+components:
+  schemas:
+    HostileElement:
+      type: object
+      properties:
+        id:
+          $ref: './openapi.yaml#/components/schemas/TypeEntityId'
+        name:
+          type: string
+          examples:
+            - animalStag
+        position:
+          $ref: './openapi.yaml#/components/schemas/TypeVector3i'
+      required:
+        - id
+        - name
+        - position
+
+    HostileList:
+      type: array
+      items:
+        $ref: '#/components/schemas/HostileElement'
+
+
+paths:
+  /api/hostile:
+    get:
+      tags:
+        - WorldState
+      summary: Hostiles list
+      description: Fetch a list of the currently spawned hostiles - including hostile animals - in the world
+      operationId: Hostile.get
+      responses:
+        200:
+          description: List of hostiles
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  data:
+                    $ref: '#/components/schemas/HostileList'
+                  meta:
+                    $ref: './openapi.yaml#/components/schemas/ResultEnvelopeMeta'
+                required:
+                  - data
+                  - meta
+        403:
+          $ref: './openapi.yaml#/components/responses/Unauthorized'
+      security:
+        - apiTokenName: []
+          apiTokenSecret: []
+        - sessionCookie: []
Index: TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Player.cs
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Player.cs	(revision 462)
+++ TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Player.cs	(revision 463)
@@ -1,4 +1,3 @@
 using System;
-using System.Collections.Generic;
 using System.Net;
 using JetBrains.Annotations;
@@ -215,45 +214,4 @@
 		}
 
-		protected override void HandleRestPost (RequestContext _context, IDictionary<string, object> _jsonInput, byte[] _jsonInputData) {
-			if (!JsonCommons.TryGetJsonField (_jsonInput, "command", out string commandString)) {
-				SendEmptyResponse (_context, HttpStatusCode.BadRequest, _jsonInputData, "NO_COMMAND");
-				return;
-			}
-
-			WebCommandResult.ResultType responseType = WebCommandResult.ResultType.Full;
-
-			if (JsonCommons.TryGetJsonField (_jsonInput, "format", out string formatString)) {
-				if (formatString.EqualsCaseInsensitive ("raw")) {
-					responseType = WebCommandResult.ResultType.Raw;
-				} else if (formatString.EqualsCaseInsensitive ("simple")) {
-					responseType = WebCommandResult.ResultType.ResultOnly;
-				}
-			}
-
-			int commandSepIndex = commandString.IndexOf (' ');
-			string commandPart = commandSepIndex > 0 ? commandString.Substring (0, commandSepIndex) : commandString;
-			string argumentsPart = commandSepIndex > 0
-				? commandString.Substring (commandPart.Length + 1)
-				: "";
-
-			IConsoleCommand command = SdtdConsole.Instance.GetCommand (commandPart, true);
-
-			if (command == null) {
-				SendEmptyResponse (_context, HttpStatusCode.NotFound, _jsonInputData, "UNKNOWN_COMMAND");
-				return;
-			}
-
-			int commandPermissionLevel = GameManager.Instance.adminTools.Commands.GetCommandPermissionLevel (command.GetCommands ());
-
-			if (_context.PermissionLevel > commandPermissionLevel) {
-				SendEmptyResponse (_context, HttpStatusCode.Forbidden, _jsonInputData, "NO_PERMISSION");
-				return;
-			}
-
-			_context.Response.SendChunked = true;
-			WebCommandResult wcr = new WebCommandResult (commandPart, argumentsPart, responseType, _context);
-			SdtdConsole.Instance.ExecuteAsync (commandString, wcr);
-		}
-
 		public override int DefaultPermissionLevel () => AdminWebModules.PermissionLevelGuest;
 	}
Index: TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Player.openapi.yaml
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Player.openapi.yaml	(revision 463)
+++ TFP-WebServer/WebServer/src/WebAPI/APIs/WorldState/Player.openapi.yaml	(revision 463)
@@ -0,0 +1,176 @@
+openapi: 3.1.0
+info:
+  title: Player
+  version: '1'
+
+components:
+  schemas:
+    PlayerElement:
+      type: object
+      properties:
+        entityId:
+          $ref: './openapi.yaml#/components/schemas/TypeEntityId'
+        name:
+          type: string
+        platformId:
+          $ref: './openapi.yaml#/components/schemas/TypeUserIdObject'
+        crossplatformId:
+          $ref: './openapi.yaml#/components/schemas/TypeUserIdObject'
+        totalPlayTimeSeconds:
+          type: 'null'
+        lastOnline:
+          type: 'null'
+        online:
+          type: boolean
+          const: true
+        ip:
+          type:
+            - string
+            - 'null'
+        ping:
+          type:
+            - integer
+            - 'null'
+        position:
+          oneOf:
+            - $ref: './openapi.yaml#/components/schemas/TypeVector3i'
+            - type: 'null'
+        level:
+          type: 'null'
+        health:
+          type: integer
+        stamina:
+          type: number
+          format: float
+        score:
+          type: integer
+        deaths:
+          type: integer
+        kills:
+          type: object
+          properties:
+            zombies:
+              type: integer
+            players:
+              type: integer
+          required:
+            - zombies
+            - players
+        banned:
+          type: object
+          properties:
+            banActive:
+              type: boolean
+            reason:
+              type:
+                - string
+                - 'null'
+            until:
+              oneOf:
+                - type: string
+                  format: date-time
+                - type: 'null'
+          required:
+            - banActive
+            - reason
+            - until
+      required:
+        - entityId
+        - name
+        - platformId
+        - crossplatformId
+        - totalPlayTimeSeconds
+        - lastOnline
+        - online
+        - ip
+        - ping
+        - position
+        - level
+        - health
+        - stamina
+        - score
+        - deaths
+        - kills
+        - banned
+
+    PlayerResultObject:
+      type: object
+      properties: 
+        players:
+          type: array
+          items:
+            $ref: '#/components/schemas/PlayerElement'
+      required:
+        - players
+
+
+  parameters:
+    EntityIdParameter:
+      name: id
+      in: path
+      required: true
+      schema:
+        $ref: './openapi.yaml#/components/schemas/TypeEntityId'
+
+
+paths:
+  /api/player:
+    get:
+      tags:
+        - WorldState
+      summary: Players list
+      description: Fetch a list of the currently online players. This is restricted to the player of the logged in user when the logged in user does not have the permission to view all players.
+      operationId: Player.get
+      responses:
+        200:
+          description: List of players
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  data:
+                    $ref: '#/components/schemas/PlayerResultObject'
+                  meta:
+                    $ref: './openapi.yaml#/components/schemas/ResultEnvelopeMeta'
+                required:
+                  - data
+                  - meta
+      security:
+        - {}
+        - apiTokenName: [ ]
+          apiTokenSecret: [ ]
+        - sessionCookie: [ ]
+
+  /api/player/{id}:
+    get:
+      tags:
+        - WorldState
+      summary: Player get
+      description: Fetch the player specified by the given EntityID
+      operationId: Player.get.id
+      parameters:
+        - $ref: '#/components/parameters/EntityIdParameter'
+      responses:
+        200:
+          description: Player
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  data:
+                    $ref: '#/components/schemas/PlayerResultObject'
+                  meta:
+                    $ref: './openapi.yaml#/components/schemas/ResultEnvelopeMeta'
+                required:
+                  - data
+                  - meta
+        404:
+          description: EntityID not found
+          $ref: './openapi.yaml#/components/responses/HttpEmptyEnvelopedResponse'
+      security:
+        - {}
+        - apiTokenName: [ ]
+          apiTokenSecret: [ ]
+        - sessionCookie: [ ]
Index: TFP-WebServer/WebServer/src/WebAPI/OpenAPI.master.yaml
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/OpenAPI.master.yaml	(revision 462)
+++ TFP-WebServer/WebServer/src/WebAPI/OpenAPI.master.yaml	(revision 463)
@@ -111,5 +111,24 @@
           description: User ID
         - type: 'null'
-
+      
+    TypeGameTimeObject:
+      type: object
+      properties:
+        days:
+          type: integer
+          minimum: 1
+        hours:
+          type: integer
+          minimum: 0
+          maximum: 23
+        minutes:
+          type: integer
+          minimum: 0
+          maximum: 59
+      required:
+        - days
+        - hours
+        - minutes
+        
 
   parameters:
Index: TFP-WebServer/WebServer/src/WebAPI/OpenApiHelpers.cs
===================================================================
--- TFP-WebServer/WebServer/src/WebAPI/OpenApiHelpers.cs	(revision 462)
+++ 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;
+				}
+			}
+		}
+
 	}
 }
