From 22518bef9ddbdaee6809e94d9efd364822bdc2b4 Mon Sep 17 00:00:00 2001
From: dvonthenen <david.vonthenen@gmail.com>
Date: Fri, 25 Oct 2024 15:12:51 -0700
Subject: [PATCH] Fix Deadlock, Bumped Interface on WS Clients, Consolidated WS
 Code

---
 Deepgram.Dev.sln                              |   20 +
 Deepgram.Tests/Fakes/ConcreteRestClient.cs    |    1 +
 .../ClientTests/AbstractRestClientTests.cs    |    1 -
 .../ClientTests/AnalyzeClientTests.cs         |    1 +
 .../UnitTests/ClientTests/ManageClientTest.cs |    3 +-
 .../ClientTests/OnPremClientTests.cs          |    1 +
 .../ClientTests/PrerecordedClientTests.cs     |    1 +
 .../UnitTests/ClientTests/SpeakClientTests.cs |    1 +
 .../UtilitiesTests/RequestContentUtilTests.cs |    2 +-
 .../Abstractions/LocalFileWithMetadata.cs     |   15 -
 .../{ => v1}/AbstractRestClient.cs            | 1564 ++++++++---------
 Deepgram/Abstractions/{ => v1}/Constants.cs   |    2 +-
 .../Abstractions/v1/LocalFileWithMetadata.cs  |   33 +
 Deepgram/Abstractions/{ => v1}/NoopSchema.cs  |    2 +-
 Deepgram/Abstractions/{ => v1}/Utilities.cs   |    2 +-
 .../Abstractions/v2/AbstractRestClient.cs     |  783 +++++++++
 .../v2/AbstractWebSocketClient.cs             |  766 ++++++++
 Deepgram/Abstractions/v2/Constants.cs         |   24 +
 .../Abstractions/v2/LocalFileWithMetadata.cs  |   33 +
 Deepgram/Abstractions/v2/NoopSchema.cs        |   13 +
 Deepgram/Abstractions/v2/Utilities.cs         |   61 +
 Deepgram/Abstractions/v2/WebSocketMessage.cs  |   32 +
 Deepgram/ClientFactory.cs                     |  115 +-
 Deepgram/Clients/Analyze/v1/Client.cs         |    1 +
 .../Clients/Interfaces/v1/ISpeakClient.cs     |   16 +-
 .../{IResponseEvent.cs => ResponseEvent.cs}   |    9 +-
 Deepgram/Clients/Interfaces/v2/Constants.cs   |   15 +
 .../Clients/Interfaces/v2/IAnalyzeClient.cs   |   94 +
 .../Interfaces/v2/IListenRESTClient.cs        |   79 +
 .../Interfaces/v2/IListenWebSocketClient.cs   |  137 ++
 Deepgram/Clients/Interfaces/v2/ILiveClient.cs |   73 +
 .../Clients/Interfaces/v2/IManageClient.cs    |  251 +++
 .../Clients/Interfaces/v2/IOnPremClient.cs    |   20 +
 .../Interfaces/v2/IPreRecordedClient.cs       |   20 +
 .../Interfaces/v2/ISelfHostedClient.cs        |   48 +
 .../Clients/Interfaces/v2/ISpeakClient.cs     |   20 +
 .../Clients/Interfaces/v2/ISpeakRESTClient.cs |   39 +
 .../Interfaces/v2/ISpeakWebSocketClient.cs    |  159 ++
 .../Clients/Interfaces/v2/ResponseEvent.cs    |   16 +
 Deepgram/Clients/Listen/v1/REST/Client.cs     |    1 +
 .../Clients/Listen/v1/WebSocket/Client.cs     |   10 +-
 .../Clients/Listen/v2/WebSocket/Client.cs     |  658 +++++++
 .../Clients/Listen/v2/WebSocket/Constants.cs  |   15 +
 .../Listen/v2/WebSocket/ResponseEvent.cs      |   11 +
 .../Listen/v2/WebSocket/UriSegments.cs        |   12 +
 Deepgram/Clients/Manage/v1/Client.cs          |    1 +
 Deepgram/Clients/SelfHosted/v1/Client.cs      |    1 +
 Deepgram/Clients/Speak/v1/REST/Client.cs      |    1 +
 Deepgram/Clients/Speak/v1/WebSocket/Client.cs |   10 +-
 .../Speak/v1/WebSocket/ResponseEvent.cs       |    9 +-
 Deepgram/Clients/Speak/v2/WebSocket/Client.cs |  711 ++++++++
 .../Clients/Speak/v2/WebSocket/Constants.cs   |   28 +
 .../Speak/v2/WebSocket/ResponseEvent.cs       |   16 +
 .../Clients/Speak/v2/WebSocket/UriSegments.cs |   12 +
 Deepgram/GlobalUsings.cs                      |    2 -
 Deepgram/ListenWebSocketClient.cs             |    2 +-
 .../Common/v2/WebSocket/CloseResponse.cs      |   37 +
 .../Common/v2/WebSocket/ErrorResponse.cs      |   61 +
 .../Common/v2/WebSocket/OpenResponse.cs       |   37 +
 .../Common/v2/WebSocket/UnhandledResponse.cs  |   45 +
 .../Common/v2/WebSocket/WebSocketType.cs      |   13 +
 .../Models/Listen/v2/WebSocket/Alternative.cs |   43 +
 .../Models/Listen/v2/WebSocket/Average.cs     |   30 +
 .../Models/Listen/v2/WebSocket/Channel.cs     |   30 +
 .../Listen/v2/WebSocket/CloseResponse.cs      |   11 +
 .../Listen/v2/WebSocket/ErrorResponse.cs      |   11 +
 Deepgram/Models/Listen/v2/WebSocket/Hit.cs    |   45 +
 .../Models/Listen/v2/WebSocket/ListenType.cs  |   19 +
 .../Models/Listen/v2/WebSocket/LiveSchema.cs  |  262 +++
 .../Models/Listen/v2/WebSocket/Metadata.cs    |   46 +
 .../Listen/v2/WebSocket/MetadataResponse.cs   |   89 +
 .../Models/Listen/v2/WebSocket/ModelInfo.cs   |   37 +
 .../Listen/v2/WebSocket/OpenResponse.cs       |   11 +
 .../Listen/v2/WebSocket/ResultResponse.cs     |   86 +
 Deepgram/Models/Listen/v2/WebSocket/Search.cs |   31 +
 .../v2/WebSocket/SpeechStartedResponse.cs     |   38 +
 .../Listen/v2/WebSocket/UnhandledResponse.cs  |   11 +
 .../v2/WebSocket/UtteranceEndResponse.cs      |   38 +
 Deepgram/Models/Listen/v2/WebSocket/Word.cs   |   65 +
 .../Speak/v1/WebSocket/AudioResponse.cs       |    2 +-
 .../Speak/v1/WebSocket/ControlMessage.cs      |    2 +-
 .../Speak/v2/WebSocket/AudioResponse.cs       |   30 +
 .../Speak/v2/WebSocket/ClearedResponse.cs     |   31 +
 .../Speak/v2/WebSocket/CloseResponse.cs       |   11 +
 .../Speak/v2/WebSocket/ControlMessage.cs      |   24 +
 .../Speak/v2/WebSocket/ErrorResponse.cs       |   11 +
 .../Speak/v2/WebSocket/FlushedResponse.cs     |   31 +
 .../Speak/v2/WebSocket/MetadataResponse.cs    |   31 +
 .../Models/Speak/v2/WebSocket/OpenResponse.cs |   11 +
 .../Models/Speak/v2/WebSocket/SpeakSchema.cs  |   56 +
 .../Models/Speak/v2/WebSocket/SpeakType.cs    |   21 +
 .../Models/Speak/v2/WebSocket/TextSource.cs   |   31 +
 .../Speak/v2/WebSocket/UnhandledResponse.cs   |   11 +
 .../Speak/v2/WebSocket/WarningResponse.cs     |   45 +
 Deepgram/SpeakWebSocketClient.cs              |    2 +-
 .../speech-to-text/websocket/file/Program.cs  |   76 +-
 .../speech-to-text/websocket/http/Program.cs  |   85 +-
 .../websocket/microphone/Program.cs           |  164 +-
 .../websocket/simple/Program.cs               |  286 +--
 tests/edge_cases/keepalive/Program.cs         |   10 +-
 .../reconnect_same_object/Program.cs          |   18 +-
 .../stt_v1_client_example/Program.cs          |   73 +
 .../stt_v1_client_example/Streaming.csproj    |   25 +
 .../tts_v1_client_example/Program.cs          |  172 ++
 .../tts_v1_client_example/Speak.csproj        |   22 +
 .../websocket/exercise_timeout/Program.cs     |   10 +-
 106 files changed, 7222 insertions(+), 1138 deletions(-)
 delete mode 100644 Deepgram/Abstractions/LocalFileWithMetadata.cs
 rename Deepgram/Abstractions/{ => v1}/AbstractRestClient.cs (95%)
 rename Deepgram/Abstractions/{ => v1}/Constants.cs (92%)
 create mode 100644 Deepgram/Abstractions/v1/LocalFileWithMetadata.cs
 rename Deepgram/Abstractions/{ => v1}/NoopSchema.cs (90%)
 rename Deepgram/Abstractions/{ => v1}/Utilities.cs (98%)
 create mode 100644 Deepgram/Abstractions/v2/AbstractRestClient.cs
 create mode 100644 Deepgram/Abstractions/v2/AbstractWebSocketClient.cs
 create mode 100644 Deepgram/Abstractions/v2/Constants.cs
 create mode 100644 Deepgram/Abstractions/v2/LocalFileWithMetadata.cs
 create mode 100644 Deepgram/Abstractions/v2/NoopSchema.cs
 create mode 100644 Deepgram/Abstractions/v2/Utilities.cs
 create mode 100644 Deepgram/Abstractions/v2/WebSocketMessage.cs
 rename Deepgram/Clients/Interfaces/v1/{IResponseEvent.cs => ResponseEvent.cs} (64%)
 create mode 100644 Deepgram/Clients/Interfaces/v2/Constants.cs
 create mode 100644 Deepgram/Clients/Interfaces/v2/IAnalyzeClient.cs
 create mode 100644 Deepgram/Clients/Interfaces/v2/IListenRESTClient.cs
 create mode 100644 Deepgram/Clients/Interfaces/v2/IListenWebSocketClient.cs
 create mode 100644 Deepgram/Clients/Interfaces/v2/ILiveClient.cs
 create mode 100644 Deepgram/Clients/Interfaces/v2/IManageClient.cs
 create mode 100644 Deepgram/Clients/Interfaces/v2/IOnPremClient.cs
 create mode 100644 Deepgram/Clients/Interfaces/v2/IPreRecordedClient.cs
 create mode 100644 Deepgram/Clients/Interfaces/v2/ISelfHostedClient.cs
 create mode 100644 Deepgram/Clients/Interfaces/v2/ISpeakClient.cs
 create mode 100644 Deepgram/Clients/Interfaces/v2/ISpeakRESTClient.cs
 create mode 100644 Deepgram/Clients/Interfaces/v2/ISpeakWebSocketClient.cs
 create mode 100644 Deepgram/Clients/Interfaces/v2/ResponseEvent.cs
 create mode 100644 Deepgram/Clients/Listen/v2/WebSocket/Client.cs
 create mode 100644 Deepgram/Clients/Listen/v2/WebSocket/Constants.cs
 create mode 100644 Deepgram/Clients/Listen/v2/WebSocket/ResponseEvent.cs
 create mode 100644 Deepgram/Clients/Listen/v2/WebSocket/UriSegments.cs
 create mode 100644 Deepgram/Clients/Speak/v2/WebSocket/Client.cs
 create mode 100644 Deepgram/Clients/Speak/v2/WebSocket/Constants.cs
 create mode 100644 Deepgram/Clients/Speak/v2/WebSocket/ResponseEvent.cs
 create mode 100644 Deepgram/Clients/Speak/v2/WebSocket/UriSegments.cs
 create mode 100644 Deepgram/Models/Common/v2/WebSocket/CloseResponse.cs
 create mode 100644 Deepgram/Models/Common/v2/WebSocket/ErrorResponse.cs
 create mode 100644 Deepgram/Models/Common/v2/WebSocket/OpenResponse.cs
 create mode 100644 Deepgram/Models/Common/v2/WebSocket/UnhandledResponse.cs
 create mode 100644 Deepgram/Models/Common/v2/WebSocket/WebSocketType.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/Alternative.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/Average.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/Channel.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/CloseResponse.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/ErrorResponse.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/Hit.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/ListenType.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/LiveSchema.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/Metadata.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/MetadataResponse.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/ModelInfo.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/OpenResponse.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/ResultResponse.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/Search.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/SpeechStartedResponse.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/UnhandledResponse.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/UtteranceEndResponse.cs
 create mode 100644 Deepgram/Models/Listen/v2/WebSocket/Word.cs
 create mode 100644 Deepgram/Models/Speak/v2/WebSocket/AudioResponse.cs
 create mode 100644 Deepgram/Models/Speak/v2/WebSocket/ClearedResponse.cs
 create mode 100644 Deepgram/Models/Speak/v2/WebSocket/CloseResponse.cs
 create mode 100644 Deepgram/Models/Speak/v2/WebSocket/ControlMessage.cs
 create mode 100644 Deepgram/Models/Speak/v2/WebSocket/ErrorResponse.cs
 create mode 100644 Deepgram/Models/Speak/v2/WebSocket/FlushedResponse.cs
 create mode 100644 Deepgram/Models/Speak/v2/WebSocket/MetadataResponse.cs
 create mode 100644 Deepgram/Models/Speak/v2/WebSocket/OpenResponse.cs
 create mode 100644 Deepgram/Models/Speak/v2/WebSocket/SpeakSchema.cs
 create mode 100644 Deepgram/Models/Speak/v2/WebSocket/SpeakType.cs
 create mode 100644 Deepgram/Models/Speak/v2/WebSocket/TextSource.cs
 create mode 100644 Deepgram/Models/Speak/v2/WebSocket/UnhandledResponse.cs
 create mode 100644 Deepgram/Models/Speak/v2/WebSocket/WarningResponse.cs
 create mode 100644 tests/edge_cases/stt_v1_client_example/Program.cs
 create mode 100644 tests/edge_cases/stt_v1_client_example/Streaming.csproj
 create mode 100644 tests/edge_cases/tts_v1_client_example/Program.cs
 create mode 100644 tests/edge_cases/tts_v1_client_example/Speak.csproj

diff --git a/Deepgram.Dev.sln b/Deepgram.Dev.sln
index 744e5ce0..d0f59b96 100644
--- a/Deepgram.Dev.sln
+++ b/Deepgram.Dev.sln
@@ -169,6 +169,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "reconnect_same_object", "re
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReconnectStreaming", "tests\edge_cases\reconnect_same_object\ReconnectStreaming.csproj", "{64AB4BAC-6917-424D-A5EA-BA023BD7795A}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "stt_v1_client_example", "stt_v1_client_example", "{0BF29CA2-1CD6-4FF0-BC7B-B33C6B41E9A1}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tts_v1_client_example", "tts_v1_client_example", "{5CEEB2F0-F284-4BB3-8999-6E91151C0C06}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Streaming", "tests\edge_cases\stt_v1_client_example\Streaming.csproj", "{964A87B4-31F8-4D68-AE64-64E66C9959FD}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Speak", "tests\edge_cases\tts_v1_client_example\Speak.csproj", "{AB053DDA-2487-476C-9793-A50C37F10CCA}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -311,6 +319,14 @@ Global
 		{64AB4BAC-6917-424D-A5EA-BA023BD7795A}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{64AB4BAC-6917-424D-A5EA-BA023BD7795A}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{64AB4BAC-6917-424D-A5EA-BA023BD7795A}.Release|Any CPU.Build.0 = Release|Any CPU
+		{964A87B4-31F8-4D68-AE64-64E66C9959FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{964A87B4-31F8-4D68-AE64-64E66C9959FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{964A87B4-31F8-4D68-AE64-64E66C9959FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{964A87B4-31F8-4D68-AE64-64E66C9959FD}.Release|Any CPU.Build.0 = Release|Any CPU
+		{AB053DDA-2487-476C-9793-A50C37F10CCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{AB053DDA-2487-476C-9793-A50C37F10CCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{AB053DDA-2487-476C-9793-A50C37F10CCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{AB053DDA-2487-476C-9793-A50C37F10CCA}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -392,6 +408,10 @@ Global
 		{ECB0B55E-54C1-4723-8641-9249E7507FB0} = {2F92D959-D3C7-4EFF-8549-C6162E517644}
 		{6E328CB9-8C9B-446B-B83E-3796804497F7} = {1280E66D-A375-422A-ACB4-48F17E9C190E}
 		{64AB4BAC-6917-424D-A5EA-BA023BD7795A} = {6E328CB9-8C9B-446B-B83E-3796804497F7}
+		{0BF29CA2-1CD6-4FF0-BC7B-B33C6B41E9A1} = {1280E66D-A375-422A-ACB4-48F17E9C190E}
+		{5CEEB2F0-F284-4BB3-8999-6E91151C0C06} = {1280E66D-A375-422A-ACB4-48F17E9C190E}
+		{964A87B4-31F8-4D68-AE64-64E66C9959FD} = {0BF29CA2-1CD6-4FF0-BC7B-B33C6B41E9A1}
+		{AB053DDA-2487-476C-9793-A50C37F10CCA} = {5CEEB2F0-F284-4BB3-8999-6E91151C0C06}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {8D4ABC6D-7126-4EE2-9303-43A954616B2A}
diff --git a/Deepgram.Tests/Fakes/ConcreteRestClient.cs b/Deepgram.Tests/Fakes/ConcreteRestClient.cs
index 8c57fdd3..ca7bb61a 100644
--- a/Deepgram.Tests/Fakes/ConcreteRestClient.cs
+++ b/Deepgram.Tests/Fakes/ConcreteRestClient.cs
@@ -3,6 +3,7 @@
 // SPDX-License-Identifier: MIT
 
 using Deepgram.Models.Authenticate.v1;
+using Deepgram.Abstractions.v1;
 
 namespace Deepgram.Tests.Fakes;
 
diff --git a/Deepgram.Tests/UnitTests/ClientTests/AbstractRestClientTests.cs b/Deepgram.Tests/UnitTests/ClientTests/AbstractRestClientTests.cs
index 91f6edb1..bbc93d6d 100644
--- a/Deepgram.Tests/UnitTests/ClientTests/AbstractRestClientTests.cs
+++ b/Deepgram.Tests/UnitTests/ClientTests/AbstractRestClientTests.cs
@@ -5,7 +5,6 @@
 using Deepgram.Models.Authenticate.v1;
 using Deepgram.Models.Manage.v1;
 using Deepgram.Models.PreRecorded.v1;
-using Deepgram.Models.Exceptions.v1;
 
 using Deepgram.Clients.Manage.v1;
 
diff --git a/Deepgram.Tests/UnitTests/ClientTests/AnalyzeClientTests.cs b/Deepgram.Tests/UnitTests/ClientTests/AnalyzeClientTests.cs
index 3eae0f22..e38678d9 100644
--- a/Deepgram.Tests/UnitTests/ClientTests/AnalyzeClientTests.cs
+++ b/Deepgram.Tests/UnitTests/ClientTests/AnalyzeClientTests.cs
@@ -5,6 +5,7 @@
 using Deepgram.Models.Authenticate.v1;
 using Deepgram.Clients.Analyze.v1;
 using Deepgram.Models.Analyze.v1;
+using Deepgram.Abstractions.v1;
 
 namespace Deepgram.Tests.UnitTests.ClientTests;
 
diff --git a/Deepgram.Tests/UnitTests/ClientTests/ManageClientTest.cs b/Deepgram.Tests/UnitTests/ClientTests/ManageClientTest.cs
index 6732e5b6..671970ac 100644
--- a/Deepgram.Tests/UnitTests/ClientTests/ManageClientTest.cs
+++ b/Deepgram.Tests/UnitTests/ClientTests/ManageClientTest.cs
@@ -5,8 +5,7 @@
 using Deepgram.Models.Authenticate.v1;
 using Deepgram.Models.Manage.v1;
 using Deepgram.Clients.Manage.v1;
-using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection;
-using NSubstitute;
+using Deepgram.Abstractions.v1;
 
 namespace Deepgram.Tests.UnitTests.ClientTests;
 
diff --git a/Deepgram.Tests/UnitTests/ClientTests/OnPremClientTests.cs b/Deepgram.Tests/UnitTests/ClientTests/OnPremClientTests.cs
index 2b786c8a..575c676b 100644
--- a/Deepgram.Tests/UnitTests/ClientTests/OnPremClientTests.cs
+++ b/Deepgram.Tests/UnitTests/ClientTests/OnPremClientTests.cs
@@ -5,6 +5,7 @@
 using Deepgram.Models.Authenticate.v1;
 using Deepgram.Models.SelfHosted.v1;
 using Deepgram.Clients.SelfHosted.v1;
+using Deepgram.Abstractions.v1;
 
 namespace Deepgram.Tests.UnitTests.ClientTests;
 
diff --git a/Deepgram.Tests/UnitTests/ClientTests/PrerecordedClientTests.cs b/Deepgram.Tests/UnitTests/ClientTests/PrerecordedClientTests.cs
index 4ca51130..335f1e08 100644
--- a/Deepgram.Tests/UnitTests/ClientTests/PrerecordedClientTests.cs
+++ b/Deepgram.Tests/UnitTests/ClientTests/PrerecordedClientTests.cs
@@ -5,6 +5,7 @@
 using Deepgram.Models.Authenticate.v1;
 using Deepgram.Clients.Listen.v1.REST;
 using Deepgram.Models.Listen.v1.REST;
+using Deepgram.Abstractions.v1;
 
 namespace Deepgram.Tests.UnitTests.ClientTests;
 
diff --git a/Deepgram.Tests/UnitTests/ClientTests/SpeakClientTests.cs b/Deepgram.Tests/UnitTests/ClientTests/SpeakClientTests.cs
index b9b84e5e..47375c6f 100644
--- a/Deepgram.Tests/UnitTests/ClientTests/SpeakClientTests.cs
+++ b/Deepgram.Tests/UnitTests/ClientTests/SpeakClientTests.cs
@@ -5,6 +5,7 @@
 using Deepgram.Models.Authenticate.v1;
 using Deepgram.Models.Speak.v1.REST;
 using Deepgram.Clients.Speak.v1.REST;
+using Deepgram.Abstractions.v1;
 
 namespace Deepgram.Tests.UnitTests.ClientTests;
 
diff --git a/Deepgram.Tests/UnitTests/UtilitiesTests/RequestContentUtilTests.cs b/Deepgram.Tests/UnitTests/UtilitiesTests/RequestContentUtilTests.cs
index cdba2d3e..c13e2b6b 100644
--- a/Deepgram.Tests/UnitTests/UtilitiesTests/RequestContentUtilTests.cs
+++ b/Deepgram.Tests/UnitTests/UtilitiesTests/RequestContentUtilTests.cs
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: MIT
 
 using Deepgram.Models.Manage.v1;
-using Deepgram.Abstractions;
+using Deepgram.Abstractions.v1;
 
 namespace Deepgram.Tests.UnitTests.UtilitiesTests;
 
diff --git a/Deepgram/Abstractions/LocalFileWithMetadata.cs b/Deepgram/Abstractions/LocalFileWithMetadata.cs
deleted file mode 100644
index 9035750d..00000000
--- a/Deepgram/Abstractions/LocalFileWithMetadata.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
-// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
-// SPDX-License-Identifier: MIT
-
-namespace Deepgram.Abstractions;
-
-/// <summary>
-/// LocalFileWithMetadata is a class that represents a file with metadata.
-/// </summary>
-public class LocalFileWithMetadata
-{
-    public Dictionary<string, string> Metadata { get; set; }
-
-    public MemoryStream Content { get; set; }
-}
diff --git a/Deepgram/Abstractions/AbstractRestClient.cs b/Deepgram/Abstractions/v1/AbstractRestClient.cs
similarity index 95%
rename from Deepgram/Abstractions/AbstractRestClient.cs
rename to Deepgram/Abstractions/v1/AbstractRestClient.cs
index 56ba7901..07decabd 100644
--- a/Deepgram/Abstractions/AbstractRestClient.cs
+++ b/Deepgram/Abstractions/v1/AbstractRestClient.cs
@@ -1,782 +1,782 @@
-// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
-// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
-// SPDX-License-Identifier: MIT
-
-using Deepgram.Encapsulations;
-using Deepgram.Models.Authenticate.v1;
-using Deepgram.Models.Exceptions.v1;
-
-namespace Deepgram.Abstractions;
-
-public abstract class AbstractRestClient
-{
-    /// <summary>
-    ///  HttpClient created by the factory
-    internal HttpClient _httpClient;
-
-    /// <summary>
-    /// Copy of the options for the client
-    /// </summary>
-    internal IDeepgramClientOptions _options;
-
-    /// <summary>
-    /// Constructor that take the options and a httpClient
-    /// </summary>
-    /// <param name="deepgramClientOptions"><see cref="_deepgramClientOptions"/>Options for the Deepgram client</param>
-
-    internal AbstractRestClient(string? apiKey = null, IDeepgramClientOptions? options = null, string? httpId = null)
-    {
-        Log.Verbose("AbstractRestClient", "ENTER");
-
-        if (options == null)
-        {
-            options = (IDeepgramClientOptions) new DeepgramHttpClientOptions(apiKey);
-        }
-        _httpClient = HttpClientFactory.Create(httpId);
-        _httpClient = HttpClientFactory.ConfigureDeepgram(_httpClient, options);
-        _options = options;
-
-        Log.Debug("AbstractRestClient", $"APIVersion: {options.APIVersion}");
-        Log.Debug("AbstractRestClient", $"BaseAddress: {options.BaseAddress}");
-        Log.Debug("AbstractRestClient", $"options: {options.OnPrem}");
-        Log.Verbose("AbstractRestClient", "LEAVE");
-    }
-
-    /// <summary>
-    /// GET Rest Request
-    /// </summary>
-    /// <typeparam name="T">Type of class of response expected</typeparam>
-    /// <param name="uriSegment">request uri Endpoint</param>
-    /// <returns>Instance of T</returns>
-    public virtual async Task<T> GetAsync<T>(string uriSegment, CancellationTokenSource? cancellationToken = null,
-    Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
-    {
-        Log.Verbose("AbstractRestClient.GetAsync<T>", "ENTER");
-        Log.Debug("GetAsync<T>", $"uriSegment: {uriSegment}");
-        Log.Debug("GetAsync<T>", $"addons: {addons}");
-
-        try
-        {
-            // if not defined, use default timeout
-            if (cancellationToken == null)
-            {
-                Log.Information("GetAsync", $"Using default timeout: {Constants.DefaultRESTTimeout}");
-                cancellationToken = new CancellationTokenSource();
-                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
-            }
-
-            // create request message and add custom query parameters
-            NoopSchema? parameter = null;
-            var request = new HttpRequestMessage(HttpMethod.Get, QueryParameterUtil.FormatURL(uriSegment, parameter, addons));
-
-            // add custom headers
-            if (headers != null)
-            {
-                foreach (var header in headers)
-                {
-                    Log.Debug("GetAsync<T>", $"Add Header {header.Key}={header.Value}");
-                    request.Headers.Add(header.Key, header.Value);
-                }
-            }
-
-            // do the request
-            Log.Verbose("GetAsync<T>", "Calling _httpClient.SendAsync...");
-            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
-
-            var resultStr = response.Content.ReadAsStringAsync().Result;
-            if (!response.IsSuccessStatusCode)
-            {
-                await ThrowException("GetAsync<T>", response, resultStr);
-            }
-
-            Log.Verbose("GetAsync<T>", $"Response:\n{resultStr}");
-            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
-
-            Log.Debug("GetAsync<T>", "Succeeded");
-            Log.Verbose("AbstractRestClient.GetAsync<T>", "LEAVE");
-            return result;
-        }
-        catch (OperationCanceledException ex)
-        {
-            Log.Information("GetAsync<T>", "Task was cancelled.");
-            Log.Verbose("GetAsync<T>", $"Connect cancelled. Info: {ex}");
-            Log.Verbose("AbstractRestClient.GetAsync<T>", "LEAVE");
-            throw;
-        }
-        catch (Exception ex)
-        {
-            Log.Error("GetAsync<T>", $"{ex.GetType()} thrown {ex.Message}");
-            Log.Verbose("GetAsync<T>", $"Excepton: {ex}");
-            Log.Verbose("AbstractRestClient.GetAsync<T>", "LEAVE");
-            throw;
-        }
-    }
-
-    public virtual async Task<T> GetAsync<S, T>(string uriSegment, S? parameter, CancellationTokenSource? cancellationToken = null,
-        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
-    {
-        Log.Verbose("AbstractRestClient.GetAsync<S, T>", "ENTER");
-        Log.Debug("GetAsync<S, T>", $"uriSegment: {uriSegment}");
-        Log.Debug("GetAsync<S, T>", $"addons: {addons}");
-
-        try
-        {
-            // if not defined, use default timeout
-            if (cancellationToken == null)
-            {
-                Log.Information("GetAsync<S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
-                cancellationToken = new CancellationTokenSource();
-                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
-            }
-
-            // create request message and add custom query parameters
-            var request = new HttpRequestMessage(HttpMethod.Get, QueryParameterUtil.FormatURL(uriSegment, parameter, addons));
-
-            // add custom headers
-            if (headers != null)
-            {
-                foreach (var header in headers)
-                {
-                    Log.Debug("GetAsync<S, T>", $"Add Header {header.Key}={header.Value}");
-                    request.Headers.Add(header.Key, header.Value);
-                }
-            }
-
-            // do the request
-            Log.Verbose("GetAsync<S, T>", "Calling _httpClient.SendAsync...");
-            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
-
-            var resultStr = response.Content.ReadAsStringAsync().Result;
-            if (!response.IsSuccessStatusCode)
-            {
-                await ThrowException("GetAsync<S, T>", response, resultStr);
-            }
-
-            Log.Verbose("GetAsync<S, T>", $"Response:\n{resultStr}");
-            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
-
-            Log.Debug("GetAsync<S, T>", "Succeeded");
-            Log.Verbose("AbstractRestClient.GetAsync<S, T>", "LEAVE");
-
-            return result;
-        }
-        catch (OperationCanceledException ex)
-        {
-            Log.Information("GetAsync<S, T>", "Task was cancelled.");
-            Log.Verbose("GetAsync<S, T>", $"Connect cancelled. Info: {ex}");
-            Log.Verbose("AbstractRestClient.GetAsync<S, T>", "LEAVE");
-            throw;
-        }
-        catch (Exception ex)
-        {
-            Log.Error("GetAsync<S, T>", $"{ex.GetType()} thrown {ex.Message}");
-            Log.Verbose("GetAsync<S, T>", $"Excepton: {ex}");
-            Log.Verbose("AbstractRestClient.GetAsync<S, T>", "LEAVE");
-            throw;
-        }
-    }
-
-    /// <summary>
-    /// Post method 
-    /// </summary>
-    /// <typeparam name="T">Class type of what return type is expected</typeparam>
-    /// <param name="uriSegment">Uri for the api including the query parameters</param> 
-    /// <param name="content">HttpContent as content for HttpRequestMessage</param>  
-    /// <returns>Instance of T</returns>
-    public virtual async Task<LocalFileWithMetadata> PostRetrieveLocalFileAsync<R, S, T>(string uriSegment, S? parameter, R? content,
-        List<string>? keys = null, CancellationTokenSource? cancellationToken = null, Dictionary<string, string>? addons = null,
-        Dictionary<string, string>? headers = null
-        )
-    {
-        Log.Verbose("AbstractRestClient.PostRetrieveLocalFileAsync<R, S, T>", "ENTER");
-        Log.Debug("PostRetrieveLocalFileAsync<R, S, T>", $"uriSegment: {uriSegment}");
-        Log.Debug("PostRetrieveLocalFileAsync<R, S, T>", $"keys: {keys}");
-        Log.Debug("PostRetrieveLocalFileAsync<R, S, T>", $"addons: {addons}");
-
-        try
-        {
-            // if not defined, use default timeout
-            if (cancellationToken == null)
-            {
-                Log.Information("PostRetrieveLocalFileAsync<R, S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
-                cancellationToken = new CancellationTokenSource();
-                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
-            }
-
-            // create request message and add custom query parameters
-            var request = new HttpRequestMessage(HttpMethod.Post, QueryParameterUtil.FormatURL(uriSegment, parameter, addons))
-            {
-                Content = HttpRequestUtil.CreatePayload(content)
-            };
-
-            // add custom headers
-            if (headers != null)
-            {
-                foreach (var header in headers)
-                {
-                    Log.Debug("PostRetrieveLocalFileAsync<R, S, T>", $"Add Header {header.Key}={header.Value}");
-                    request.Headers.Add(header.Key, header.Value);
-                }
-            }
-
-            // do the request
-            Log.Verbose("PostRetrieveLocalFileAsync<R, S, T>", "Calling _httpClient.SendAsync...");
-            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
-
-            if (!response.IsSuccessStatusCode)
-            {
-                await ThrowException("PostRetrieveLocalFileAsync<R, S, T>", response, response.Content.ReadAsStringAsync().Result);
-            }
-
-            var result = new Dictionary<string, string>();
-
-            if (keys != null)
-            {
-                for (int i = 0; i < response.Headers.Count(); i++)
-                {
-                    var key = response.Headers.ElementAt(i).Key.ToLower();
-                    var value = response.Headers.GetValues(key).FirstOrDefault() ?? "";
-
-                    var index = key.IndexOf("x-dg-");
-                    if (index == 0)
-                    {
-                        var newKey = key.Substring(5);
-                        if (keys.Contains(newKey))
-                        {
-                            result.Add(newKey, value);
-                            continue;
-                        }
-                    }
-                    index = key.IndexOf("dg-");
-                    if (index == 0)
-                    {
-                        var newKey = key.Substring(3);
-                        if (keys.Contains(newKey))
-                        {
-                            result.Add(newKey, value);
-                            continue;
-                        }
-                    }
-                    if (keys.Contains(key))
-                    {
-                        result.Add(key, value);
-                    }
-                }
-
-                if (keys.Contains("content-type"))
-                {
-                    result.Add("content-type", response.Content.Headers.ContentType?.MediaType ?? "");
-                }
-            }
-
-            MemoryStream stream = new MemoryStream();
-            await response.Content.CopyToAsync(stream);
-
-            Log.Verbose("PostRetrieveLocalFileAsync<R, S, T>", "Succeeded");
-            Log.Verbose("AbstractRestClient.PostRetrieveLocalFileAsync<R, S, T>", "LEAVE");
-
-            return new LocalFileWithMetadata()
-                        {
-                            Metadata = result,
-                            Content = stream,
-                        };
-
-        }
-        catch (OperationCanceledException ex)
-        {
-            Log.Information("PostRetrieveLocalFileAsync<R, S, T>", "Task was cancelled.");
-            Log.Verbose("PostRetrieveLocalFileAsync<R, S, T>", $"Connect cancelled. Info: {ex}");
-            Log.Verbose("AbstractRestClient.PostRetrieveLocalFileAsync<R, S, T>", "LEAVE");
-            throw;
-        }
-        catch (Exception ex)
-        {
-            Log.Error("PostRetrieveLocalFileAsync<R, S, T>", $"{ex.GetType()} thrown {ex.Message}");
-            Log.Verbose("PostRetrieveLocalFileAsync<R, S, T>", $"Excepton: {ex}");
-            Log.Verbose("AbstractRestClient.PostRetrieveLocalFileAsync<R, S, T>", "LEAVE");
-            throw;
-        }
-    }
-
-    /// <summary>
-    /// Post method 
-    /// </summary>
-    /// <typeparam name="T">Class type of what return type is expected</typeparam>
-    /// <param name="uriSegment">Uri for the api including the query parameters</param> 
-    /// <param name="content">HttpContent as content for HttpRequestMessage</param>  
-    /// <returns>Instance of T</returns>
-    public virtual async Task<T> PostAsync<S, T>(string uriSegment, S? parameter, CancellationTokenSource? cancellationToken = null,
-        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
-    {
-        Log.Verbose("AbstractRestClient.PostAsync<S, T>", "ENTER");
-        Log.Debug("PostAsync<S, T>", $"uriSegment: {uriSegment}");
-        Log.Debug("PostAsync<S, T>", $"addons: {addons}");
-
-        try
-        {
-            // if not defined, use default timeout
-            if (cancellationToken == null)
-            {
-                Log.Information("PostAsync<S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
-                cancellationToken = new CancellationTokenSource();
-                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
-            }
-
-            // create request message and add custom query parameters
-            var request = new HttpRequestMessage(HttpMethod.Post, QueryParameterUtil.FormatURL(uriSegment, parameter, addons))
-            {
-                Content = HttpRequestUtil.CreatePayload(parameter)
-            };
-
-            // add custom headers
-            if (headers != null)
-            {
-                foreach (var header in headers)
-                {
-                    Log.Debug("PostAsync<S, T>", $"Add Header {header.Key}={header.Value}");
-                    request.Headers.Add(header.Key, header.Value);
-                }
-            }
-
-            // do the request
-            Log.Verbose("PostAsync<S, T>", "Calling _httpClient.SendAsync...");
-            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
-
-            var resultStr = response.Content.ReadAsStringAsync().Result;
-            if (!response.IsSuccessStatusCode)
-            {
-                await ThrowException("PostAsync<S, T>", response, resultStr);
-            }
-
-            Log.Verbose("PostAsync<S, T>", $"Response:\n{resultStr}");
-            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
-
-            Log.Debug("PostAsync<S, T>", $"Succeeded");
-            Log.Verbose("AbstractRestClient.PostAsync<S, T>", "LEAVE");
-
-            return result;
-        }
-        catch (OperationCanceledException ex)
-        {
-            Log.Information("PostAsync<S, T>", "Task was cancelled.");
-            Log.Verbose("PostAsync<S, T>", $"Connect cancelled. Info: {ex}");
-            Log.Verbose("AbstractRestClient.PostAsync<S, T>", "LEAVE");
-            throw;
-        }
-        catch (Exception ex)
-        {
-            Log.Error("PostAsync<S, T>", $"{ex.GetType()} thrown {ex.Message}");
-            Log.Verbose("PostAsync<S, T>", $"Excepton: {ex}");
-            Log.Verbose("AbstractRestClient.PostAsync<S, T>", "LEAVE");
-            throw;
-        }
-    }
-
-    public virtual async Task<T> PostAsync<R, S, T>(string uriSegment, S? parameter, R? content, CancellationTokenSource? cancellationToken = null,
-        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
-    {
-        Log.Verbose("AbstractRestClient.PostAsync<R, S, T>", "ENTER");
-        Log.Debug("PostAsync<S, T>", $"uriSegment: {uriSegment}");
-        Log.Debug("PostAsync<R, S, T>", $"addons: {addons}");
-
-        try
-        {
-            // if not defined, use default timeout
-            if (cancellationToken == null)
-            {
-                Log.Information("PostAsync<R, S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
-                cancellationToken = new CancellationTokenSource();
-                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
-            }
-
-            // create request message and add custom query parameters
-            var request = new HttpRequestMessage(HttpMethod.Post, QueryParameterUtil.FormatURL(uriSegment, parameter, addons));
-            if (typeof(R) == typeof(Stream))
-            {
-                Stream? stream = content as Stream;
-                if (stream == null)
-                {
-                    stream = new MemoryStream();
-                }
-                request.Content = HttpRequestUtil.CreateStreamPayload(stream);
-            }
-            else
-            {
-                request.Content = HttpRequestUtil.CreatePayload(content);
-            }
-
-            // add custom headers
-            if (headers != null)
-            {
-                foreach (var header in headers)
-                {
-                    Log.Debug("PostAsync<R, S, T>", $"Add Header {header.Key}={header.Value}");
-                    request.Headers.Add(header.Key, header.Value);
-                }
-            }
-
-            // do the request
-            Log.Verbose("PostAsync<R, S, T>", "Calling _httpClient.SendAsync...");
-            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
-
-            var resultStr = response.Content.ReadAsStringAsync().Result;
-            if (!response.IsSuccessStatusCode)
-            {
-                await ThrowException("PostAsync<R, S, T>", response, resultStr);
-            }
-
-            Log.Verbose("PostAsync<R, S, T>", $"Response:\n{resultStr}");
-            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
-
-            Log.Debug("PostAsync<R, S, T>", $"Succeeded");
-            Log.Verbose("AbstractRestClient.PostAsync<R, S, T>", "LEAVE");
-
-            return result;
-        }
-        catch (OperationCanceledException ex)
-        {
-            Log.Information("PostAsync<R, S, T>", "Task was cancelled.");
-            Log.Verbose("PostAsync<R, S, T>", $"Connect cancelled. Info: {ex}");
-            Log.Verbose("AbstractRestClient.PostAsync<R, S, T>", "LEAVE");
-            throw;
-        }
-        catch (Exception ex)
-        {
-            Log.Error("PostAsync<R, S, T>", $"{ex.GetType()} thrown {ex.Message}");
-            Log.Verbose("PostAsync<R, S, T>", $"Excepton: {ex}");
-            Log.Verbose("AbstractRestClient.PostAsync<R, S, T>", "LEAVE");
-            throw;
-        }
-    }
-
-    /// <summary>
-    /// Patch method call that takes a body object
-    /// </summary>
-    /// <typeparam name="T">Class type of what return type is expected</typeparam>
-    /// <param name="uriSegment">Uri for the api including the query parameters</param>  
-    /// <returns>Instance of T</returns>
-    public virtual async Task<T> PatchAsync<S, T>(string uriSegment, S? parameter, CancellationTokenSource? cancellationToken = null,
-        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
-    {
-        Log.Verbose("AbstractRestClient.PatchAsync<S, T>", "ENTER");
-        Log.Debug("PatchAsync<S, T>", $"uriSegment: {uriSegment}");
-        Log.Debug("PatchAsync<S, T>", $"addons: {addons}");
-
-        try
-        {
-            // if not defined, use default timeout
-            if (cancellationToken == null)
-            {
-                Log.Information("PatchAsync<S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
-                cancellationToken = new CancellationTokenSource();
-                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
-            }
-
-            // create request message and add custom query parameters
-#if NETSTANDARD2_0
-            var request = new HttpRequestMessage(new HttpMethod("PATCH"), QueryParameterUtil.FormatURL(uriSegment, parameter, addons))
-            {
-                Content = HttpRequestUtil.CreatePayload(parameter)
-            };
-#else
-            var request = new HttpRequestMessage(HttpMethod.Patch, QueryParameterUtil.FormatURL(uriSegment, parameter, addons))
-            {
-                Content = HttpRequestUtil.CreatePayload(parameter)
-            };
-#endif
-
-            // add custom headers
-            if (headers != null)
-            {
-                foreach (var header in headers)
-                {
-                    Log.Debug("PatchAsync<S, T>", $"Add Header {header.Key}={header.Value}");
-                    request.Headers.Add(header.Key, header.Value);
-                }
-            }
-
-            // do the request
-            Log.Verbose("PatchAsync<S, T>", "Calling _httpClient.SendAsync...");
-            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
-
-            var resultStr = response.Content.ReadAsStringAsync().Result;
-            if (!response.IsSuccessStatusCode)
-            {
-                await ThrowException("PatchAsync<S, T>", response, resultStr);
-            }
-
-            Log.Verbose("PatchAsync<S, T>", $"Response:\n{resultStr}");
-            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
-
-            Log.Debug("PatchAsync<S, T>", $"Succeeded");
-            Log.Verbose("AbstractRestClient.PatchAsync<S, T>", "LEAVE");
-
-            return result;
-
-        }
-        catch (OperationCanceledException ex)
-        {
-            Log.Information("PatchAsync<S, T>", "Task was cancelled.");
-            Log.Verbose("PatchAsync<S, T>", $"Connect cancelled. Info: {ex}");
-            Log.Verbose("AbstractRestClient.PatchAsync<S, T>", "LEAVE");
-            throw;
-        }
-        catch (Exception ex)
-        {
-            Log.Error("PatchAsync<S, T>", $"{ex.GetType()} thrown {ex.Message}");
-            Log.Verbose("PatchAsync<S, T>", $"Excepton: {ex}");
-            Log.Verbose("AbstractRestClient.PatchAsync<S, T>", "LEAVE");
-            throw;
-        }
-    }
-
-    /// <summary>
-    /// Put method call that takes a body object
-    /// </summary>
-    /// <typeparam name="T">Class type of what return type is expected</typeparam>
-    /// <param name="uriSegment">Uri for the api</param>   
-    /// <returns>Instance of T</returns>
-    public virtual async Task<T> PutAsync<S, T>(string uriSegment, S? parameter, CancellationTokenSource? cancellationToken = null,
-        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
-    {
-        Log.Verbose("AbstractRestClient.PutAsync<S, T>", "ENTER");
-        Log.Debug("PutAsync<S, T>", $"uriSegment: {uriSegment}");
-        Log.Debug("PutAsync<S, T>", $"addons: {addons}");
-
-        try
-        {
-            // if not defined, use default timeout
-            if (cancellationToken == null)
-            {
-                Log.Information("PutAsync<S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
-                cancellationToken = new CancellationTokenSource();
-                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
-            }
-
-            // create request message and add custom query parameters
-            var request = new HttpRequestMessage(HttpMethod.Put, QueryParameterUtil.FormatURL(uriSegment, parameter, addons))
-            {
-                Content = HttpRequestUtil.CreatePayload(parameter)
-            };
-
-            // add custom headers
-            if (headers != null)
-            {
-                foreach (var header in headers)
-                {
-                    var tmp = header.Key.ToLower();
-                    if ( !(tmp.Contains("password") || tmp.Contains("token") || tmp.Contains("authorization") || tmp.Contains("auth")) )
-                    {
-                        Log.Debug("PutAsync<S, T>", $"Add Header {header.Key}={header.Value}");
-                    }
-                    request.Headers.Add(header.Key, header.Value);
-                }
-            }
-
-            // do the request
-            Log.Verbose("PutAsync<S, T>", "Calling _httpClient.SendAsync...");
-            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
-
-            var resultStr = response.Content.ReadAsStringAsync().Result;
-            if (!response.IsSuccessStatusCode)
-            {
-                await ThrowException("PutAsync<S, T>", response, resultStr);
-            }
-
-            Log.Verbose("PutAsync<S, T>", $"Response:\n{resultStr}");
-            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
-
-            Log.Debug("PutAsync<S, T>", $"Succeeded");
-            Log.Verbose("AbstractRestClient.PutAsync<S, T>", "LEAVE");
-
-            return result;
-        }
-        catch (OperationCanceledException ex)
-        {
-            Log.Information("PutAsync<S, T>", "Task was cancelled.");
-            Log.Verbose("PutAsync<S, T>", $"Connect cancelled. Info: {ex}");
-            Log.Verbose("AbstractRestClient.PutAsync<S, T>", "LEAVE");
-            throw;
-        }
-        catch (Exception ex)
-        {
-            Log.Error("PutAsync<S, T>", $"{ex.GetType()} thrown {ex.Message}");
-            Log.Verbose("PutAsync<S, T>", $"Excepton: {ex}");
-            Log.Verbose("AbstractRestClient.PutAsync<S, T>", "LEAVE");
-            throw;
-        }
-    }
-
-    /// <summary>
-    /// Delete Method for use with calls that do not expect a response
-    /// </summary>
-    /// <param name="uriSegment">Uri for the api including the query parameters</param> 
-    public virtual async Task<T> DeleteAsync<T>(string uriSegment, CancellationTokenSource? cancellationToken = null,
-        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
-    {
-        Log.Verbose("AbstractRestClient.DeleteAsync<T>", "ENTER");
-        Log.Debug("DeleteAsync<T>", $"uriSegment: {uriSegment}");
-        Log.Debug("DeleteAsync<T>", $"addons: {addons}");
-
-        try
-        {
-            // if not defined, use default timeout
-            if (cancellationToken == null)
-            {
-                Log.Information("DeleteAsync<T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
-                cancellationToken = new CancellationTokenSource();
-                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
-            }
-
-            // create request message and add custom query parameters
-            var request = new HttpRequestMessage(HttpMethod.Delete, QueryParameterUtil.FormatURL(uriSegment, new NoopSchema(), addons));
-
-            // add custom headers
-            if (headers != null)
-            {
-                foreach (var header in headers)
-                {
-                    Log.Debug("DeleteAsync<T>", $"Add Header {header.Key}={header.Value}");
-                    request.Headers.Add(header.Key, header.Value);
-                }
-            }
-
-            // do the request
-            Log.Verbose("DeleteAsync<T>", "Calling _httpClient.SendAsync...");
-            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
-
-            var resultStr = response.Content.ReadAsStringAsync().Result;
-            if (!response.IsSuccessStatusCode)
-            {
-                await ThrowException("DeleteAsync<T>", response, resultStr);
-            }
-
-            Log.Verbose("DeleteAsync<T>", $"Response:\n{resultStr}");
-            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
-
-            Log.Debug("DeleteAsync<T>", $"Succeeded");
-            Log.Verbose("AbstractRestClient.DeleteAsync<T>", "LEAVE");
-
-            return result;
-        }
-        catch (OperationCanceledException ex)
-        {
-            Log.Information("DeleteAsync<T>", "Task was cancelled.");
-            Log.Verbose("DeleteAsync<T>", $"Connect cancelled. Info: {ex}");
-            Log.Verbose("AbstractRestClient.DeleteAsync<T>", "LEAVE");
-            throw;
-        }
-        catch (Exception ex)
-        {
-            Log.Error("DeleteAsync<T>", $"{ex.GetType()} thrown {ex.Message}");
-            Log.Verbose("DeleteAsync<T>", $"Excepton: {ex}");
-            Log.Verbose("AbstractRestClient.DeleteAsync<T>", "LEAVE");
-            throw;
-        }
-    }
-
-    /// <summary>
-    /// Delete method that returns the type of response specified
-    /// </summary>
-    /// <typeparam name="T">Class Type of expected response</typeparam>
-    /// <param name="uriSegment">Uri for the api including the query parameters</param>      
-    /// <returns>Instance  of T or throws Exception</returns>
-    public virtual async Task<T> DeleteAsync<S, T>(string uriSegment, S? parameter, CancellationTokenSource? cancellationToken = null, Dictionary<string, string>? addons = null,
-        Dictionary<string, string>? headers = null)
-    {
-        Log.Verbose("AbstractRestClient.DeleteAsync<S, T>", "ENTER");
-        Log.Debug("DeleteAsync<S, T>", $"uriSegment: {uriSegment}");
-        Log.Debug("DeleteAsync<S, T>", $"addons: {addons}");
-
-        try
-        {
-            // if not defined, use default timeout
-            if (cancellationToken == null)
-            {
-                Log.Information("DeleteAsync<S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
-                cancellationToken = new CancellationTokenSource();
-                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
-            }
-
-            // create request message and add custom query parameters
-            var request = new HttpRequestMessage(HttpMethod.Delete, QueryParameterUtil.FormatURL(uriSegment, parameter, addons));
-
-            // add custom headers
-            if (headers != null)
-            {
-                foreach (var header in headers)
-                {
-                    Log.Debug("DeleteAsync<S, T>", $"Add Header {header.Key}={header.Value}");
-                    request.Headers.Add(header.Key, header.Value);
-                }
-            }
-
-            // do the request
-            Log.Verbose("DeleteAsync<S, T>", "Calling _httpClient.SendAsync...");
-            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
-
-            var resultStr = response.Content.ReadAsStringAsync().Result;
-            if (!response.IsSuccessStatusCode)
-            {
-                await ThrowException("DeleteAsync<S, T>", response, resultStr);
-            }
-
-            Log.Verbose("DeleteAsync<S, T>", $"Response:\n{resultStr}");
-            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
-
-            Log.Debug("DeleteAsync<S, T>", $"Succeeded");
-            Log.Verbose("AbstractRestClient.DeleteAsync<S, T>", "LEAVE");
-
-            return result;
-        }
-        catch (OperationCanceledException ex)
-        {
-            Log.Information("DeleteAsync<S, T>", "Task was cancelled.");
-            Log.Verbose("DeleteAsync<S, T>", $"Connect cancelled. Info: {ex}");
-            Log.Verbose("AbstractRestClient.DeleteAsync<S, T>", "LEAVE");
-            throw;
-        }
-        catch (Exception ex)
-        {
-            Log.Error("DeleteAsync<S, T>", $"{ex.GetType()} thrown {ex.Message}");
-            Log.Verbose("DeleteAsync<S, T>", $"Excepton: {ex}");
-            Log.Verbose("AbstractRestClient.DeleteAsync<S, T>", "LEAVE");
-            throw;
-        }
-    }
-
-    private static async Task ThrowException(string module, HttpResponseMessage response, string errMsg)
-    {
-        if (errMsg == null || errMsg.Length == 0)
-        {
-            Log.Verbose(module, $"HTTP/REST Exception thrown");
-            response.EnsureSuccessStatusCode(); // this throws the exception
-        }
-
-        Log.Verbose(module, $"Deepgram Exception: {errMsg}");
-        DeepgramRESTException? resException = null;
-        try
-        {
-            resException = await HttpRequestUtil.DeserializeAsync<DeepgramRESTException>(response);
-        }
-        catch (Exception ex)
-        {
-            Log.Verbose(module, $"DeserializeAsync Error Exception: {ex}");
-        }
-
-        if (resException != null)
-        {
-            Log.Verbose(module, "DeepgramRESTException thrown");
-            throw resException;
-        }
-
-        Log.Verbose(module, $"Deepgram Generic Exception thrown");
-        throw new DeepgramException(errMsg);
-    }
-
-    internal static string GetUri(IDeepgramClientOptions options, string path)
-    {
-        return $"{options.BaseAddress}/{path}";
-    }
-}
-
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Encapsulations;
+using Deepgram.Models.Authenticate.v1;
+using Deepgram.Models.Exceptions.v1;
+
+namespace Deepgram.Abstractions.v1;
+
+public abstract class AbstractRestClient
+{
+    /// <summary>
+    ///  HttpClient created by the factory
+    internal HttpClient _httpClient;
+
+    /// <summary>
+    /// Copy of the options for the client
+    /// </summary>
+    internal IDeepgramClientOptions _options;
+
+    /// <summary>
+    /// Constructor that take the options and a httpClient
+    /// </summary>
+    /// <param name="deepgramClientOptions"><see cref="_deepgramClientOptions"/>Options for the Deepgram client</param>
+
+    internal AbstractRestClient(string? apiKey = null, IDeepgramClientOptions? options = null, string? httpId = null)
+    {
+        Log.Verbose("AbstractRestClient", "ENTER");
+
+        if (options == null)
+        {
+            options = new DeepgramHttpClientOptions(apiKey);
+        }
+        _httpClient = HttpClientFactory.Create(httpId);
+        _httpClient = HttpClientFactory.ConfigureDeepgram(_httpClient, options);
+        _options = options;
+
+        Log.Debug("AbstractRestClient", $"APIVersion: {options.APIVersion}");
+        Log.Debug("AbstractRestClient", $"BaseAddress: {options.BaseAddress}");
+        Log.Debug("AbstractRestClient", $"options: {options.OnPrem}");
+        Log.Verbose("AbstractRestClient", "LEAVE");
+    }
+
+    /// <summary>
+    /// GET Rest Request
+    /// </summary>
+    /// <typeparam name="T">Type of class of response expected</typeparam>
+    /// <param name="uriSegment">request uri Endpoint</param>
+    /// <returns>Instance of T</returns>
+    public virtual async Task<T> GetAsync<T>(string uriSegment, CancellationTokenSource? cancellationToken = null,
+    Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.GetAsync<T>", "ENTER");
+        Log.Debug("GetAsync<T>", $"uriSegment: {uriSegment}");
+        Log.Debug("GetAsync<T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("GetAsync", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            NoopSchema? parameter = null;
+            var request = new HttpRequestMessage(HttpMethod.Get, QueryParameterUtil.FormatURL(uriSegment, parameter, addons));
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("GetAsync<T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("GetAsync<T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = response.Content.ReadAsStringAsync().Result;
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("GetAsync<T>", response, resultStr);
+            }
+
+            Log.Verbose("GetAsync<T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("GetAsync<T>", "Succeeded");
+            Log.Verbose("AbstractRestClient.GetAsync<T>", "LEAVE");
+            return result;
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("GetAsync<T>", "Task was cancelled.");
+            Log.Verbose("GetAsync<T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.GetAsync<T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("GetAsync<T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("GetAsync<T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.GetAsync<T>", "LEAVE");
+            throw;
+        }
+    }
+
+    public virtual async Task<T> GetAsync<S, T>(string uriSegment, S? parameter, CancellationTokenSource? cancellationToken = null,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.GetAsync<S, T>", "ENTER");
+        Log.Debug("GetAsync<S, T>", $"uriSegment: {uriSegment}");
+        Log.Debug("GetAsync<S, T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("GetAsync<S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            var request = new HttpRequestMessage(HttpMethod.Get, QueryParameterUtil.FormatURL(uriSegment, parameter, addons));
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("GetAsync<S, T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("GetAsync<S, T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = response.Content.ReadAsStringAsync().Result;
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("GetAsync<S, T>", response, resultStr);
+            }
+
+            Log.Verbose("GetAsync<S, T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("GetAsync<S, T>", "Succeeded");
+            Log.Verbose("AbstractRestClient.GetAsync<S, T>", "LEAVE");
+
+            return result;
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("GetAsync<S, T>", "Task was cancelled.");
+            Log.Verbose("GetAsync<S, T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.GetAsync<S, T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("GetAsync<S, T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("GetAsync<S, T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.GetAsync<S, T>", "LEAVE");
+            throw;
+        }
+    }
+
+    /// <summary>
+    /// Post method 
+    /// </summary>
+    /// <typeparam name="T">Class type of what return type is expected</typeparam>
+    /// <param name="uriSegment">Uri for the api including the query parameters</param> 
+    /// <param name="content">HttpContent as content for HttpRequestMessage</param>  
+    /// <returns>Instance of T</returns>
+    public virtual async Task<LocalFileWithMetadata> PostRetrieveLocalFileAsync<R, S, T>(string uriSegment, S? parameter, R? content,
+        List<string>? keys = null, CancellationTokenSource? cancellationToken = null, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null
+        )
+    {
+        Log.Verbose("AbstractRestClient.PostRetrieveLocalFileAsync<R, S, T>", "ENTER");
+        Log.Debug("PostRetrieveLocalFileAsync<R, S, T>", $"uriSegment: {uriSegment}");
+        Log.Debug("PostRetrieveLocalFileAsync<R, S, T>", $"keys: {keys}");
+        Log.Debug("PostRetrieveLocalFileAsync<R, S, T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("PostRetrieveLocalFileAsync<R, S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            var request = new HttpRequestMessage(HttpMethod.Post, QueryParameterUtil.FormatURL(uriSegment, parameter, addons))
+            {
+                Content = HttpRequestUtil.CreatePayload(content)
+            };
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("PostRetrieveLocalFileAsync<R, S, T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("PostRetrieveLocalFileAsync<R, S, T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("PostRetrieveLocalFileAsync<R, S, T>", response, response.Content.ReadAsStringAsync().Result);
+            }
+
+            var result = new Dictionary<string, string>();
+
+            if (keys != null)
+            {
+                for (var i = 0; i < response.Headers.Count(); i++)
+                {
+                    var key = response.Headers.ElementAt(i).Key.ToLower();
+                    var value = response.Headers.GetValues(key).FirstOrDefault() ?? "";
+
+                    var index = key.IndexOf("x-dg-");
+                    if (index == 0)
+                    {
+                        var newKey = key.Substring(5);
+                        if (keys.Contains(newKey))
+                        {
+                            result.Add(newKey, value);
+                            continue;
+                        }
+                    }
+                    index = key.IndexOf("dg-");
+                    if (index == 0)
+                    {
+                        var newKey = key.Substring(3);
+                        if (keys.Contains(newKey))
+                        {
+                            result.Add(newKey, value);
+                            continue;
+                        }
+                    }
+                    if (keys.Contains(key))
+                    {
+                        result.Add(key, value);
+                    }
+                }
+
+                if (keys.Contains("content-type"))
+                {
+                    result.Add("content-type", response.Content.Headers.ContentType?.MediaType ?? "");
+                }
+            }
+
+            var stream = new MemoryStream();
+            await response.Content.CopyToAsync(stream);
+
+            Log.Verbose("PostRetrieveLocalFileAsync<R, S, T>", "Succeeded");
+            Log.Verbose("AbstractRestClient.PostRetrieveLocalFileAsync<R, S, T>", "LEAVE");
+
+            return new LocalFileWithMetadata()
+            {
+                Metadata = result,
+                Content = stream,
+            };
+
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("PostRetrieveLocalFileAsync<R, S, T>", "Task was cancelled.");
+            Log.Verbose("PostRetrieveLocalFileAsync<R, S, T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.PostRetrieveLocalFileAsync<R, S, T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("PostRetrieveLocalFileAsync<R, S, T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("PostRetrieveLocalFileAsync<R, S, T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.PostRetrieveLocalFileAsync<R, S, T>", "LEAVE");
+            throw;
+        }
+    }
+
+    /// <summary>
+    /// Post method 
+    /// </summary>
+    /// <typeparam name="T">Class type of what return type is expected</typeparam>
+    /// <param name="uriSegment">Uri for the api including the query parameters</param> 
+    /// <param name="content">HttpContent as content for HttpRequestMessage</param>  
+    /// <returns>Instance of T</returns>
+    public virtual async Task<T> PostAsync<S, T>(string uriSegment, S? parameter, CancellationTokenSource? cancellationToken = null,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.PostAsync<S, T>", "ENTER");
+        Log.Debug("PostAsync<S, T>", $"uriSegment: {uriSegment}");
+        Log.Debug("PostAsync<S, T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("PostAsync<S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            var request = new HttpRequestMessage(HttpMethod.Post, QueryParameterUtil.FormatURL(uriSegment, parameter, addons))
+            {
+                Content = HttpRequestUtil.CreatePayload(parameter)
+            };
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("PostAsync<S, T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("PostAsync<S, T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = response.Content.ReadAsStringAsync().Result;
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("PostAsync<S, T>", response, resultStr);
+            }
+
+            Log.Verbose("PostAsync<S, T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("PostAsync<S, T>", $"Succeeded");
+            Log.Verbose("AbstractRestClient.PostAsync<S, T>", "LEAVE");
+
+            return result;
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("PostAsync<S, T>", "Task was cancelled.");
+            Log.Verbose("PostAsync<S, T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.PostAsync<S, T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("PostAsync<S, T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("PostAsync<S, T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.PostAsync<S, T>", "LEAVE");
+            throw;
+        }
+    }
+
+    public virtual async Task<T> PostAsync<R, S, T>(string uriSegment, S? parameter, R? content, CancellationTokenSource? cancellationToken = null,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.PostAsync<R, S, T>", "ENTER");
+        Log.Debug("PostAsync<S, T>", $"uriSegment: {uriSegment}");
+        Log.Debug("PostAsync<R, S, T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("PostAsync<R, S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            var request = new HttpRequestMessage(HttpMethod.Post, QueryParameterUtil.FormatURL(uriSegment, parameter, addons));
+            if (typeof(R) == typeof(Stream))
+            {
+                var stream = content as Stream;
+                if (stream == null)
+                {
+                    stream = new MemoryStream();
+                }
+                request.Content = HttpRequestUtil.CreateStreamPayload(stream);
+            }
+            else
+            {
+                request.Content = HttpRequestUtil.CreatePayload(content);
+            }
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("PostAsync<R, S, T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("PostAsync<R, S, T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = response.Content.ReadAsStringAsync().Result;
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("PostAsync<R, S, T>", response, resultStr);
+            }
+
+            Log.Verbose("PostAsync<R, S, T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("PostAsync<R, S, T>", $"Succeeded");
+            Log.Verbose("AbstractRestClient.PostAsync<R, S, T>", "LEAVE");
+
+            return result;
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("PostAsync<R, S, T>", "Task was cancelled.");
+            Log.Verbose("PostAsync<R, S, T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.PostAsync<R, S, T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("PostAsync<R, S, T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("PostAsync<R, S, T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.PostAsync<R, S, T>", "LEAVE");
+            throw;
+        }
+    }
+
+    /// <summary>
+    /// Patch method call that takes a body object
+    /// </summary>
+    /// <typeparam name="T">Class type of what return type is expected</typeparam>
+    /// <param name="uriSegment">Uri for the api including the query parameters</param>  
+    /// <returns>Instance of T</returns>
+    public virtual async Task<T> PatchAsync<S, T>(string uriSegment, S? parameter, CancellationTokenSource? cancellationToken = null,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.PatchAsync<S, T>", "ENTER");
+        Log.Debug("PatchAsync<S, T>", $"uriSegment: {uriSegment}");
+        Log.Debug("PatchAsync<S, T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("PatchAsync<S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+#if NETSTANDARD2_0
+            var request = new HttpRequestMessage(new HttpMethod("PATCH"), QueryParameterUtil.FormatURL(uriSegment, parameter, addons))
+            {
+                Content = HttpRequestUtil.CreatePayload(parameter)
+            };
+#else
+            var request = new HttpRequestMessage(HttpMethod.Patch, QueryParameterUtil.FormatURL(uriSegment, parameter, addons))
+            {
+                Content = HttpRequestUtil.CreatePayload(parameter)
+            };
+#endif
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("PatchAsync<S, T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("PatchAsync<S, T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = response.Content.ReadAsStringAsync().Result;
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("PatchAsync<S, T>", response, resultStr);
+            }
+
+            Log.Verbose("PatchAsync<S, T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("PatchAsync<S, T>", $"Succeeded");
+            Log.Verbose("AbstractRestClient.PatchAsync<S, T>", "LEAVE");
+
+            return result;
+
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("PatchAsync<S, T>", "Task was cancelled.");
+            Log.Verbose("PatchAsync<S, T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.PatchAsync<S, T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("PatchAsync<S, T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("PatchAsync<S, T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.PatchAsync<S, T>", "LEAVE");
+            throw;
+        }
+    }
+
+    /// <summary>
+    /// Put method call that takes a body object
+    /// </summary>
+    /// <typeparam name="T">Class type of what return type is expected</typeparam>
+    /// <param name="uriSegment">Uri for the api</param>   
+    /// <returns>Instance of T</returns>
+    public virtual async Task<T> PutAsync<S, T>(string uriSegment, S? parameter, CancellationTokenSource? cancellationToken = null,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.PutAsync<S, T>", "ENTER");
+        Log.Debug("PutAsync<S, T>", $"uriSegment: {uriSegment}");
+        Log.Debug("PutAsync<S, T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("PutAsync<S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            var request = new HttpRequestMessage(HttpMethod.Put, QueryParameterUtil.FormatURL(uriSegment, parameter, addons))
+            {
+                Content = HttpRequestUtil.CreatePayload(parameter)
+            };
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    var tmp = header.Key.ToLower();
+                    if (!(tmp.Contains("password") || tmp.Contains("token") || tmp.Contains("authorization") || tmp.Contains("auth")))
+                    {
+                        Log.Debug("PutAsync<S, T>", $"Add Header {header.Key}={header.Value}");
+                    }
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("PutAsync<S, T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = response.Content.ReadAsStringAsync().Result;
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("PutAsync<S, T>", response, resultStr);
+            }
+
+            Log.Verbose("PutAsync<S, T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("PutAsync<S, T>", $"Succeeded");
+            Log.Verbose("AbstractRestClient.PutAsync<S, T>", "LEAVE");
+
+            return result;
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("PutAsync<S, T>", "Task was cancelled.");
+            Log.Verbose("PutAsync<S, T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.PutAsync<S, T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("PutAsync<S, T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("PutAsync<S, T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.PutAsync<S, T>", "LEAVE");
+            throw;
+        }
+    }
+
+    /// <summary>
+    /// Delete Method for use with calls that do not expect a response
+    /// </summary>
+    /// <param name="uriSegment">Uri for the api including the query parameters</param> 
+    public virtual async Task<T> DeleteAsync<T>(string uriSegment, CancellationTokenSource? cancellationToken = null,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.DeleteAsync<T>", "ENTER");
+        Log.Debug("DeleteAsync<T>", $"uriSegment: {uriSegment}");
+        Log.Debug("DeleteAsync<T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("DeleteAsync<T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            var request = new HttpRequestMessage(HttpMethod.Delete, QueryParameterUtil.FormatURL(uriSegment, new NoopSchema(), addons));
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("DeleteAsync<T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("DeleteAsync<T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = response.Content.ReadAsStringAsync().Result;
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("DeleteAsync<T>", response, resultStr);
+            }
+
+            Log.Verbose("DeleteAsync<T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("DeleteAsync<T>", $"Succeeded");
+            Log.Verbose("AbstractRestClient.DeleteAsync<T>", "LEAVE");
+
+            return result;
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("DeleteAsync<T>", "Task was cancelled.");
+            Log.Verbose("DeleteAsync<T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.DeleteAsync<T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("DeleteAsync<T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("DeleteAsync<T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.DeleteAsync<T>", "LEAVE");
+            throw;
+        }
+    }
+
+    /// <summary>
+    /// Delete method that returns the type of response specified
+    /// </summary>
+    /// <typeparam name="T">Class Type of expected response</typeparam>
+    /// <param name="uriSegment">Uri for the api including the query parameters</param>      
+    /// <returns>Instance  of T or throws Exception</returns>
+    public virtual async Task<T> DeleteAsync<S, T>(string uriSegment, S? parameter, CancellationTokenSource? cancellationToken = null, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.DeleteAsync<S, T>", "ENTER");
+        Log.Debug("DeleteAsync<S, T>", $"uriSegment: {uriSegment}");
+        Log.Debug("DeleteAsync<S, T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("DeleteAsync<S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            var request = new HttpRequestMessage(HttpMethod.Delete, QueryParameterUtil.FormatURL(uriSegment, parameter, addons));
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("DeleteAsync<S, T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("DeleteAsync<S, T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = response.Content.ReadAsStringAsync().Result;
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("DeleteAsync<S, T>", response, resultStr);
+            }
+
+            Log.Verbose("DeleteAsync<S, T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("DeleteAsync<S, T>", $"Succeeded");
+            Log.Verbose("AbstractRestClient.DeleteAsync<S, T>", "LEAVE");
+
+            return result;
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("DeleteAsync<S, T>", "Task was cancelled.");
+            Log.Verbose("DeleteAsync<S, T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.DeleteAsync<S, T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("DeleteAsync<S, T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("DeleteAsync<S, T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.DeleteAsync<S, T>", "LEAVE");
+            throw;
+        }
+    }
+
+    private static async Task ThrowException(string module, HttpResponseMessage response, string errMsg)
+    {
+        if (errMsg == null || errMsg.Length == 0)
+        {
+            Log.Verbose(module, $"HTTP/REST Exception thrown");
+            response.EnsureSuccessStatusCode(); // this throws the exception
+        }
+
+        Log.Verbose(module, $"Deepgram Exception: {errMsg}");
+        DeepgramRESTException? resException = null;
+        try
+        {
+            resException = await HttpRequestUtil.DeserializeAsync<DeepgramRESTException>(response);
+        }
+        catch (Exception ex)
+        {
+            Log.Verbose(module, $"DeserializeAsync Error Exception: {ex}");
+        }
+
+        if (resException != null)
+        {
+            Log.Verbose(module, "DeepgramRESTException thrown");
+            throw resException;
+        }
+
+        Log.Verbose(module, $"Deepgram Generic Exception thrown");
+        throw new DeepgramException(errMsg);
+    }
+
+    internal static string GetUri(IDeepgramClientOptions options, string path)
+    {
+        return $"{options.BaseAddress}/{path}";
+    }
+}
+
diff --git a/Deepgram/Abstractions/Constants.cs b/Deepgram/Abstractions/v1/Constants.cs
similarity index 92%
rename from Deepgram/Abstractions/Constants.cs
rename to Deepgram/Abstractions/v1/Constants.cs
index 97fa3ebd..9dec95fb 100644
--- a/Deepgram/Abstractions/Constants.cs
+++ b/Deepgram/Abstractions/v1/Constants.cs
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT license that can be found in the LICENSE file.
 // SPDX-License-Identifier: MIT
 
-namespace Deepgram.Abstractions;
+namespace Deepgram.Abstractions.v1;
 
 /// <summary> 
 /// Defaults for the REST Client
diff --git a/Deepgram/Abstractions/v1/LocalFileWithMetadata.cs b/Deepgram/Abstractions/v1/LocalFileWithMetadata.cs
new file mode 100644
index 00000000..ee1f1830
--- /dev/null
+++ b/Deepgram/Abstractions/v1/LocalFileWithMetadata.cs
@@ -0,0 +1,33 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Abstractions.v1;
+
+/// <summary>
+/// LocalFileWithMetadata is a class that represents a file with metadata.
+/// </summary>
+public class LocalFileWithMetadata
+{
+    /// <summary>
+    /// Gets or sets the metadata associated with the file content.
+    /// </summary>
+    public Dictionary<string, string> Metadata { get; set; }
+
+    /// <summary>
+    /// Gets or sets the file content as a MemoryStream.
+    /// The caller is responsible for disposing of this stream when no longer needed.
+    /// </summary>
+    /// <remarks>
+    /// This property should be properly disposed of to prevent memory leaks.
+    /// </remarks>
+     public MemoryStream Content { get; set; }
+
+    /// <summary>
+    /// Releases the resources used by the Content stream.
+    /// </summary>
+    public void Dispose()
+    {
+        Content?.Dispose();
+    }
+}
diff --git a/Deepgram/Abstractions/NoopSchema.cs b/Deepgram/Abstractions/v1/NoopSchema.cs
similarity index 90%
rename from Deepgram/Abstractions/NoopSchema.cs
rename to Deepgram/Abstractions/v1/NoopSchema.cs
index 4af49920..1ff0ab67 100644
--- a/Deepgram/Abstractions/NoopSchema.cs
+++ b/Deepgram/Abstractions/v1/NoopSchema.cs
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT license that can be found in the LICENSE file.
 // SPDX-License-Identifier: MIT
 
-namespace Deepgram.Abstractions;
+namespace Deepgram.Abstractions.v1;
 
 /// <summary>
 /// Just a NoopSchema where you dont need to marshall JSON into a Async function.
diff --git a/Deepgram/Abstractions/Utilities.cs b/Deepgram/Abstractions/v1/Utilities.cs
similarity index 98%
rename from Deepgram/Abstractions/Utilities.cs
rename to Deepgram/Abstractions/v1/Utilities.cs
index 1a4e8b38..b6b1e4d3 100644
--- a/Deepgram/Abstractions/Utilities.cs
+++ b/Deepgram/Abstractions/v1/Utilities.cs
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT license that can be found in the LICENSE file.
 // SPDX-License-Identifier: MIT
 
-namespace Deepgram.Abstractions;
+namespace Deepgram.Abstractions.v1;
 
 /// <summary>
 /// Helper funcitons for HttpRequests
diff --git a/Deepgram/Abstractions/v2/AbstractRestClient.cs b/Deepgram/Abstractions/v2/AbstractRestClient.cs
new file mode 100644
index 00000000..0e165d18
--- /dev/null
+++ b/Deepgram/Abstractions/v2/AbstractRestClient.cs
@@ -0,0 +1,783 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Encapsulations;
+using Deepgram.Models.Authenticate.v1;
+using Deepgram.Models.Exceptions.v1;
+
+namespace Deepgram.Abstractions.v2;
+
+public abstract class AbstractRestClient
+{
+    /// <summary>
+    ///  HttpClient created by the factory
+    internal HttpClient _httpClient;
+
+    /// <summary>
+    /// Copy of the options for the client
+    /// </summary>
+    internal IDeepgramClientOptions _options;
+
+    /// <summary>
+    /// Constructor that take the options and a httpClient
+    /// </summary>
+    /// <param name="deepgramClientOptions"><see cref="_deepgramClientOptions"/>Options for the Deepgram client</param>
+
+    internal AbstractRestClient(string? apiKey = null, IDeepgramClientOptions? options = null, string? httpId = null)
+    {
+        Log.Verbose("AbstractRestClient", "ENTER");
+
+        if (options == null)
+        {
+            options = (IDeepgramClientOptions) new DeepgramHttpClientOptions(apiKey);
+        }
+        _httpClient = HttpClientFactory.Create(httpId);
+        _httpClient = HttpClientFactory.ConfigureDeepgram(_httpClient, options);
+        _options = options;
+
+        Log.Debug("AbstractRestClient", $"APIVersion: {options.APIVersion}");
+        Log.Debug("AbstractRestClient", $"BaseAddress: {options.BaseAddress}");
+        Log.Debug("AbstractRestClient", $"options: {options.OnPrem}");
+        Log.Verbose("AbstractRestClient", "LEAVE");
+    }
+
+    /// <summary>
+    /// GET Rest Request
+    /// </summary>
+    /// <typeparam name="T">Type of class of response expected</typeparam>
+    /// <param name="uriSegment">request uri Endpoint</param>
+    /// <returns>Instance of T</returns>
+    public virtual async Task<T> GetAsync<T>(string uriSegment, CancellationTokenSource? cancellationToken = null,
+    Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.GetAsync<T>", "ENTER");
+        Log.Debug("GetAsync<T>", $"uriSegment: {uriSegment}");
+        Log.Debug("GetAsync<T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("GetAsync", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            NoopSchema? parameter = null;
+            var request = new HttpRequestMessage(HttpMethod.Get, QueryParameterUtil.FormatURL(uriSegment, parameter, addons));
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("GetAsync<T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("GetAsync<T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = await response.Content.ReadAsStringAsync();
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("GetAsync<T>", response, resultStr);
+            }
+
+            Log.Verbose("GetAsync<T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("GetAsync<T>", "Succeeded");
+            Log.Verbose("AbstractRestClient.GetAsync<T>", "LEAVE");
+            return result;
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("GetAsync<T>", "Task was cancelled.");
+            Log.Verbose("GetAsync<T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.GetAsync<T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("GetAsync<T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("GetAsync<T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.GetAsync<T>", "LEAVE");
+            throw;
+        }
+    }
+
+    public virtual async Task<T> GetAsync<S, T>(string uriSegment, S? parameter, CancellationTokenSource? cancellationToken = null,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.GetAsync<S, T>", "ENTER");
+        Log.Debug("GetAsync<S, T>", $"uriSegment: {uriSegment}");
+        Log.Debug("GetAsync<S, T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("GetAsync<S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            var request = new HttpRequestMessage(HttpMethod.Get, QueryParameterUtil.FormatURL(uriSegment, parameter, addons));
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("GetAsync<S, T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("GetAsync<S, T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = await response.Content.ReadAsStringAsync();
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("GetAsync<S, T>", response, resultStr);
+            }
+
+            Log.Verbose("GetAsync<S, T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("GetAsync<S, T>", "Succeeded");
+            Log.Verbose("AbstractRestClient.GetAsync<S, T>", "LEAVE");
+
+            return result;
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("GetAsync<S, T>", "Task was cancelled.");
+            Log.Verbose("GetAsync<S, T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.GetAsync<S, T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("GetAsync<S, T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("GetAsync<S, T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.GetAsync<S, T>", "LEAVE");
+            throw;
+        }
+    }
+
+    /// <summary>
+    /// Post method 
+    /// </summary>
+    /// <typeparam name="T">Class type of what return type is expected</typeparam>
+    /// <param name="uriSegment">Uri for the api including the query parameters</param> 
+    /// <param name="content">HttpContent as content for HttpRequestMessage</param>  
+    /// <returns>Instance of T</returns>
+    public virtual async Task<LocalFileWithMetadata> PostRetrieveLocalFileAsync<R, S, T>(string uriSegment, S? parameter, R? content,
+        List<string>? keys = null, CancellationTokenSource? cancellationToken = null, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null
+        )
+    {
+        Log.Verbose("AbstractRestClient.PostRetrieveLocalFileAsync<R, S, T>", "ENTER");
+        Log.Debug("PostRetrieveLocalFileAsync<R, S, T>", $"uriSegment: {uriSegment}");
+        Log.Debug("PostRetrieveLocalFileAsync<R, S, T>", $"keys: {keys}");
+        Log.Debug("PostRetrieveLocalFileAsync<R, S, T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("PostRetrieveLocalFileAsync<R, S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            var request = new HttpRequestMessage(HttpMethod.Post, QueryParameterUtil.FormatURL(uriSegment, parameter, addons))
+            {
+                Content = HttpRequestUtil.CreatePayload(content)
+            };
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("PostRetrieveLocalFileAsync<R, S, T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("PostRetrieveLocalFileAsync<R, S, T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            if (!response.IsSuccessStatusCode)
+            {
+                var resultStr = await response.Content.ReadAsStringAsync();
+                await ThrowException("PostRetrieveLocalFileAsync<R, S, T>", response, resultStr);
+            }
+
+            var result = new Dictionary<string, string>();
+
+            if (keys != null)
+            {
+                for (int i = 0; i < response.Headers.Count(); i++)
+                {
+                    var key = response.Headers.ElementAt(i).Key.ToLower();
+                    var value = response.Headers.GetValues(key).FirstOrDefault() ?? "";
+
+                    var index = key.IndexOf("x-dg-");
+                    if (index == 0)
+                    {
+                        var newKey = key.Substring(5);
+                        if (keys.Contains(newKey))
+                        {
+                            result.Add(newKey, value);
+                            continue;
+                        }
+                    }
+                    index = key.IndexOf("dg-");
+                    if (index == 0)
+                    {
+                        var newKey = key.Substring(3);
+                        if (keys.Contains(newKey))
+                        {
+                            result.Add(newKey, value);
+                            continue;
+                        }
+                    }
+                    if (keys.Contains(key))
+                    {
+                        result.Add(key, value);
+                    }
+                }
+
+                if (keys.Contains("content-type"))
+                {
+                    result.Add("content-type", response.Content.Headers.ContentType?.MediaType ?? "");
+                }
+            }
+
+            MemoryStream stream = new MemoryStream();
+            await response.Content.CopyToAsync(stream);
+
+            Log.Verbose("PostRetrieveLocalFileAsync<R, S, T>", "Succeeded");
+            Log.Verbose("AbstractRestClient.PostRetrieveLocalFileAsync<R, S, T>", "LEAVE");
+
+            return new LocalFileWithMetadata()
+                        {
+                            Metadata = result,
+                            Content = stream,
+                        };
+
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("PostRetrieveLocalFileAsync<R, S, T>", "Task was cancelled.");
+            Log.Verbose("PostRetrieveLocalFileAsync<R, S, T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.PostRetrieveLocalFileAsync<R, S, T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("PostRetrieveLocalFileAsync<R, S, T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("PostRetrieveLocalFileAsync<R, S, T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.PostRetrieveLocalFileAsync<R, S, T>", "LEAVE");
+            throw;
+        }
+    }
+
+    /// <summary>
+    /// Post method 
+    /// </summary>
+    /// <typeparam name="T">Class type of what return type is expected</typeparam>
+    /// <param name="uriSegment">Uri for the api including the query parameters</param> 
+    /// <param name="content">HttpContent as content for HttpRequestMessage</param>  
+    /// <returns>Instance of T</returns>
+    public virtual async Task<T> PostAsync<S, T>(string uriSegment, S? parameter, CancellationTokenSource? cancellationToken = null,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.PostAsync<S, T>", "ENTER");
+        Log.Debug("PostAsync<S, T>", $"uriSegment: {uriSegment}");
+        Log.Debug("PostAsync<S, T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("PostAsync<S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            var request = new HttpRequestMessage(HttpMethod.Post, QueryParameterUtil.FormatURL(uriSegment, parameter, addons))
+            {
+                Content = HttpRequestUtil.CreatePayload(parameter)
+            };
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("PostAsync<S, T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("PostAsync<S, T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = await response.Content.ReadAsStringAsync();
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("PostAsync<S, T>", response, resultStr);
+            }
+
+            Log.Verbose("PostAsync<S, T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("PostAsync<S, T>", $"Succeeded");
+            Log.Verbose("AbstractRestClient.PostAsync<S, T>", "LEAVE");
+
+            return result;
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("PostAsync<S, T>", "Task was cancelled.");
+            Log.Verbose("PostAsync<S, T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.PostAsync<S, T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("PostAsync<S, T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("PostAsync<S, T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.PostAsync<S, T>", "LEAVE");
+            throw;
+        }
+    }
+
+    public virtual async Task<T> PostAsync<R, S, T>(string uriSegment, S? parameter, R? content, CancellationTokenSource? cancellationToken = null,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.PostAsync<R, S, T>", "ENTER");
+        Log.Debug("PostAsync<S, T>", $"uriSegment: {uriSegment}");
+        Log.Debug("PostAsync<R, S, T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("PostAsync<R, S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            var request = new HttpRequestMessage(HttpMethod.Post, QueryParameterUtil.FormatURL(uriSegment, parameter, addons));
+            if (typeof(R) == typeof(Stream))
+            {
+                Stream? stream = content as Stream;
+                if (stream == null)
+                {
+                    stream = new MemoryStream();
+                }
+                request.Content = HttpRequestUtil.CreateStreamPayload(stream);
+            }
+            else
+            {
+                request.Content = HttpRequestUtil.CreatePayload(content);
+            }
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("PostAsync<R, S, T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("PostAsync<R, S, T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = await response.Content.ReadAsStringAsync();
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("PostAsync<R, S, T>", response, resultStr);
+            }
+
+            Log.Verbose("PostAsync<R, S, T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("PostAsync<R, S, T>", $"Succeeded");
+            Log.Verbose("AbstractRestClient.PostAsync<R, S, T>", "LEAVE");
+
+            return result;
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("PostAsync<R, S, T>", "Task was cancelled.");
+            Log.Verbose("PostAsync<R, S, T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.PostAsync<R, S, T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("PostAsync<R, S, T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("PostAsync<R, S, T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.PostAsync<R, S, T>", "LEAVE");
+            throw;
+        }
+    }
+
+    /// <summary>
+    /// Patch method call that takes a body object
+    /// </summary>
+    /// <typeparam name="T">Class type of what return type is expected</typeparam>
+    /// <param name="uriSegment">Uri for the api including the query parameters</param>  
+    /// <returns>Instance of T</returns>
+    public virtual async Task<T> PatchAsync<S, T>(string uriSegment, S? parameter, CancellationTokenSource? cancellationToken = null,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.PatchAsync<S, T>", "ENTER");
+        Log.Debug("PatchAsync<S, T>", $"uriSegment: {uriSegment}");
+        Log.Debug("PatchAsync<S, T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("PatchAsync<S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+#if NETSTANDARD2_0
+            var request = new HttpRequestMessage(new HttpMethod("PATCH"), QueryParameterUtil.FormatURL(uriSegment, parameter, addons))
+            {
+                Content = HttpRequestUtil.CreatePayload(parameter)
+            };
+#else
+            var request = new HttpRequestMessage(HttpMethod.Patch, QueryParameterUtil.FormatURL(uriSegment, parameter, addons))
+            {
+                Content = HttpRequestUtil.CreatePayload(parameter)
+            };
+#endif
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("PatchAsync<S, T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("PatchAsync<S, T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = await response.Content.ReadAsStringAsync();
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("PatchAsync<S, T>", response, resultStr);
+            }
+
+            Log.Verbose("PatchAsync<S, T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("PatchAsync<S, T>", $"Succeeded");
+            Log.Verbose("AbstractRestClient.PatchAsync<S, T>", "LEAVE");
+
+            return result;
+
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("PatchAsync<S, T>", "Task was cancelled.");
+            Log.Verbose("PatchAsync<S, T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.PatchAsync<S, T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("PatchAsync<S, T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("PatchAsync<S, T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.PatchAsync<S, T>", "LEAVE");
+            throw;
+        }
+    }
+
+    /// <summary>
+    /// Put method call that takes a body object
+    /// </summary>
+    /// <typeparam name="T">Class type of what return type is expected</typeparam>
+    /// <param name="uriSegment">Uri for the api</param>   
+    /// <returns>Instance of T</returns>
+    public virtual async Task<T> PutAsync<S, T>(string uriSegment, S? parameter, CancellationTokenSource? cancellationToken = null,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.PutAsync<S, T>", "ENTER");
+        Log.Debug("PutAsync<S, T>", $"uriSegment: {uriSegment}");
+        Log.Debug("PutAsync<S, T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("PutAsync<S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            var request = new HttpRequestMessage(HttpMethod.Put, QueryParameterUtil.FormatURL(uriSegment, parameter, addons))
+            {
+                Content = HttpRequestUtil.CreatePayload(parameter)
+            };
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    var tmp = header.Key.ToLower();
+                    if ( !(tmp.Contains("password") || tmp.Contains("token") || tmp.Contains("authorization") || tmp.Contains("auth")) )
+                    {
+                        Log.Debug("PutAsync<S, T>", $"Add Header {header.Key}={header.Value}");
+                    }
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("PutAsync<S, T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = await response.Content.ReadAsStringAsync();
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("PutAsync<S, T>", response, resultStr);
+            }
+
+            Log.Verbose("PutAsync<S, T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("PutAsync<S, T>", $"Succeeded");
+            Log.Verbose("AbstractRestClient.PutAsync<S, T>", "LEAVE");
+
+            return result;
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("PutAsync<S, T>", "Task was cancelled.");
+            Log.Verbose("PutAsync<S, T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.PutAsync<S, T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("PutAsync<S, T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("PutAsync<S, T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.PutAsync<S, T>", "LEAVE");
+            throw;
+        }
+    }
+
+    /// <summary>
+    /// Delete Method for use with calls that do not expect a response
+    /// </summary>
+    /// <param name="uriSegment">Uri for the api including the query parameters</param> 
+    public virtual async Task<T> DeleteAsync<T>(string uriSegment, CancellationTokenSource? cancellationToken = null,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.DeleteAsync<T>", "ENTER");
+        Log.Debug("DeleteAsync<T>", $"uriSegment: {uriSegment}");
+        Log.Debug("DeleteAsync<T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("DeleteAsync<T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            var request = new HttpRequestMessage(HttpMethod.Delete, QueryParameterUtil.FormatURL(uriSegment, new NoopSchema(), addons));
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("DeleteAsync<T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("DeleteAsync<T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = await response.Content.ReadAsStringAsync();
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("DeleteAsync<T>", response, resultStr);
+            }
+
+            Log.Verbose("DeleteAsync<T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("DeleteAsync<T>", $"Succeeded");
+            Log.Verbose("AbstractRestClient.DeleteAsync<T>", "LEAVE");
+
+            return result;
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("DeleteAsync<T>", "Task was cancelled.");
+            Log.Verbose("DeleteAsync<T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.DeleteAsync<T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("DeleteAsync<T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("DeleteAsync<T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.DeleteAsync<T>", "LEAVE");
+            throw;
+        }
+    }
+
+    /// <summary>
+    /// Delete method that returns the type of response specified
+    /// </summary>
+    /// <typeparam name="T">Class Type of expected response</typeparam>
+    /// <param name="uriSegment">Uri for the api including the query parameters</param>      
+    /// <returns>Instance  of T or throws Exception</returns>
+    public virtual async Task<T> DeleteAsync<S, T>(string uriSegment, S? parameter, CancellationTokenSource? cancellationToken = null, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractRestClient.DeleteAsync<S, T>", "ENTER");
+        Log.Debug("DeleteAsync<S, T>", $"uriSegment: {uriSegment}");
+        Log.Debug("DeleteAsync<S, T>", $"addons: {addons}");
+
+        try
+        {
+            // if not defined, use default timeout
+            if (cancellationToken == null)
+            {
+                Log.Information("DeleteAsync<S, T>", $"Using default timeout: {Constants.DefaultRESTTimeout}");
+                cancellationToken = new CancellationTokenSource();
+                cancellationToken.CancelAfter(Constants.DefaultRESTTimeout);
+            }
+
+            // create request message and add custom query parameters
+            var request = new HttpRequestMessage(HttpMethod.Delete, QueryParameterUtil.FormatURL(uriSegment, parameter, addons));
+
+            // add custom headers
+            if (headers != null)
+            {
+                foreach (var header in headers)
+                {
+                    Log.Debug("DeleteAsync<S, T>", $"Add Header {header.Key}={header.Value}");
+                    request.Headers.Add(header.Key, header.Value);
+                }
+            }
+
+            // do the request
+            Log.Verbose("DeleteAsync<S, T>", "Calling _httpClient.SendAsync...");
+            var response = await _httpClient.SendAsync(request, cancellationToken.Token);
+
+            var resultStr = await response.Content.ReadAsStringAsync();
+            if (!response.IsSuccessStatusCode)
+            {
+                await ThrowException("DeleteAsync<S, T>", response, resultStr);
+            }
+
+            Log.Verbose("DeleteAsync<S, T>", $"Response:\n{resultStr}");
+            var result = await HttpRequestUtil.DeserializeAsync<T>(response);
+
+            Log.Debug("DeleteAsync<S, T>", $"Succeeded");
+            Log.Verbose("AbstractRestClient.DeleteAsync<S, T>", "LEAVE");
+
+            return result;
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Information("DeleteAsync<S, T>", "Task was cancelled.");
+            Log.Verbose("DeleteAsync<S, T>", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractRestClient.DeleteAsync<S, T>", "LEAVE");
+            throw;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("DeleteAsync<S, T>", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("DeleteAsync<S, T>", $"Excepton: {ex}");
+            Log.Verbose("AbstractRestClient.DeleteAsync<S, T>", "LEAVE");
+            throw;
+        }
+    }
+
+    private static async Task ThrowException(string module, HttpResponseMessage response, string errMsg)
+    {
+        if (errMsg == null || errMsg.Length == 0)
+        {
+            Log.Verbose(module, $"HTTP/REST Exception thrown");
+            response.EnsureSuccessStatusCode(); // this throws the exception
+        }
+
+        Log.Verbose(module, $"Deepgram Exception: {errMsg}");
+        DeepgramRESTException? resException = null;
+        try
+        {
+            resException = await HttpRequestUtil.DeserializeAsync<DeepgramRESTException>(response);
+        }
+        catch (Exception ex)
+        {
+            Log.Verbose(module, $"DeserializeAsync Error Exception: {ex}");
+        }
+
+        if (resException != null)
+        {
+            Log.Verbose(module, "DeepgramRESTException thrown");
+            throw resException;
+        }
+
+        Log.Verbose(module, $"Deepgram Generic Exception thrown");
+        throw new DeepgramException(errMsg);
+    }
+
+    internal static string GetUri(IDeepgramClientOptions options, string path)
+    {
+        return $"{options.BaseAddress}/{path}";
+    }
+}
+
diff --git a/Deepgram/Abstractions/v2/AbstractWebSocketClient.cs b/Deepgram/Abstractions/v2/AbstractWebSocketClient.cs
new file mode 100644
index 00000000..16bdbec1
--- /dev/null
+++ b/Deepgram/Abstractions/v2/AbstractWebSocketClient.cs
@@ -0,0 +1,766 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+
+using Deepgram.Models.Authenticate.v1;
+using Deepgram.Models.Exceptions.v1;
+using Deepgram.Models.Common.v2.WebSocket;
+
+namespace Deepgram.Abstractions.v2;
+
+/// <summary>
+/// Implements version 1 of the Live Client.
+/// </summary>
+public abstract class AbstractWebSocketClient : IDisposable
+{
+    #region Fields
+    protected readonly IDeepgramClientOptions _deepgramClientOptions;
+
+    protected ClientWebSocket? _clientWebSocket;
+    protected CancellationTokenSource? _cancellationTokenSource;
+
+    protected readonly SemaphoreSlim _mutexSubscribe = new SemaphoreSlim(1, 1);
+    protected readonly SemaphoreSlim _mutexSend = new SemaphoreSlim(1, 1);
+    #endregion
+
+    /// <param name="apiKey">Required DeepgramApiKey</param>
+    /// <param name="deepgramClientOptions"><see cref="IDeepgramClientOptions"/> for HttpClient Configuration</param>
+    public AbstractWebSocketClient(string? apiKey = null, IDeepgramClientOptions? options = null)
+    {
+        Log.Verbose("AbstractWebSocketClient", "ENTER");
+
+        options ??= new DeepgramWsClientOptions(apiKey);
+        _deepgramClientOptions = options;
+
+        Log.Debug("AbstractWebSocketClient", $"APIVersion: {options.APIVersion}");
+        Log.Debug("AbstractWebSocketClient", $"BaseAddress: {options.BaseAddress}");
+        Log.Debug("AbstractWebSocketClient", $"OnPrem: {options.OnPrem}");
+        Log.Verbose("AbstractWebSocketClient", "LEAVE");
+    }
+
+    #region Event Handlers
+    /// <summary>
+    /// Fires when an event is received from the Deepgram API
+    /// </summary>
+    protected event EventHandler<OpenResponse>? _openReceived;
+    protected event EventHandler<CloseResponse>? _closeReceived;
+    protected event EventHandler<UnhandledResponse>? _unhandledReceived;
+    protected event EventHandler<ErrorResponse>? _errorReceived;
+    #endregion
+
+    /// <summary>
+    /// Connect to a Deepgram API Web Socket to begin transcribing audio
+    /// </summary>
+    /// <param name="options">Options to use when transcribing audio</param>
+    /// <returns>The task object representing the asynchronous operation.</returns>
+    public async Task<bool> Connect(string uri, CancellationTokenSource? cancelToken = null, Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("AbstractWebSocketClient.Connect", "ENTER");
+        Log.Debug("Connect", $"headers: {headers}");
+
+        // check if the client is disposed
+        if (_clientWebSocket != null)
+        {
+            // client has already connected
+            var exStr = "Client has already been initialized";
+            Log.Error("Connect", exStr);
+            Log.Verbose("AbstractWebSocketClient.Connect", "LEAVE");
+
+            return true;
+        }
+
+        if (cancelToken == null)
+        {
+            Log.Information("Connect", "Using default connect cancellation token");
+            cancelToken = new CancellationTokenSource(Constants.DefaultConnectTimeout);
+        }
+
+        // create client
+        _clientWebSocket = new ClientWebSocket();
+
+        // set headers
+        _clientWebSocket.Options.SetRequestHeader("Authorization", $"token {_deepgramClientOptions.ApiKey}");
+        if (_deepgramClientOptions.Headers is not null) {
+            foreach (var header in _deepgramClientOptions.Headers)
+            {
+                var tmp = header.Key.ToLower();
+                if (!(tmp.Contains("password") || tmp.Contains("token") || tmp.Contains("authorization") || tmp.Contains("auth")))
+                {
+                    Log.Debug("PutAsync<S, T>", $"Add Header {header.Key}={header.Value}");
+                }
+                _clientWebSocket.Options.SetRequestHeader(header.Key, header.Value);
+            }
+        }
+        if (headers is not null)
+        {
+            foreach (var header in headers)
+            {
+                var tmp = header.Key.ToLower();
+                if (!(tmp.Contains("password") || tmp.Contains("token") || tmp.Contains("authorization") || tmp.Contains("auth")))
+                {
+                    Log.Debug("PutAsync<S, T>", $"Add Header {header.Key}={header.Value}");
+                }
+                _clientWebSocket.Options.SetRequestHeader(header.Key, header.Value);
+            }
+        }
+
+        // internal cancellation token for internal threads
+        _cancellationTokenSource = new CancellationTokenSource();
+
+        try
+        {
+            var myUri = new Uri(uri);
+            Log.Debug("Connect", $"uri: {uri}");
+
+            Log.Debug("Connect", "Connecting to Deepgram API...");
+            await _clientWebSocket.ConnectAsync(myUri, cancelToken.Token).ConfigureAwait(false);
+
+            if (!IsConnected())
+            {
+                Log.Error("Connect", "Failed to connect to Deepgram API");
+                Log.Verbose("AbstractWebSocketClient.Connect", "LEAVE");
+
+                return false;
+            }
+
+            Log.Debug("Connect", "Starting Sender Thread...");
+            StartSenderBackgroundThread();
+
+            Log.Debug("Connect", "Starting Receiver Thread...");
+            StartReceiverBackgroundThread();
+
+            // send an OpenResponse event
+            if (_openReceived != null)
+            {
+                Log.Debug("Connect", "Sending OpenResponse event...");
+                var data = new OpenResponse();
+                data.Type = WebSocketType.Open;
+                _openReceived.Invoke(null, data);
+            }
+
+            Log.Debug("Connect", "Connect Succeeded");
+            Log.Verbose("AbstractWebSocketClient.Connect", "LEAVE");
+
+            return true;
+        }
+        catch (TaskCanceledException ex)
+        {
+            Log.Debug("Connect", "Connect cancelled.");
+            Log.Verbose("Connect", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("AbstractWebSocketClient.Connect", "LEAVE");
+
+            return false;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("Connect", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("Connect", $"Exception: {ex}");
+            Log.Verbose("AbstractWebSocketClient.Connect", "LEAVE");
+            throw;
+        }
+
+        void StartSenderBackgroundThread() => Task.Run(() => ProcessSendQueue());
+
+        void StartReceiverBackgroundThread() => Task.Run(() => ProcessReceiveQueue());
+    }
+
+    #region Subscribe Event
+    /// <summary>
+    /// Subscribe to an Open event from the Deepgram API
+    /// </summary>
+    /// <param name="eventHandler"></param>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<OpenResponse> eventHandler)
+    {
+        await _mutexSubscribe.WaitAsync();
+        try
+        {
+            _openReceived += (sender, e) => eventHandler(sender, e);
+        }
+        finally
+        {
+            _mutexSubscribe.Release();
+        }
+
+        return true;
+    }
+
+    /// <summary>
+    /// Subscribe to a Close event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<CloseResponse> eventHandler)
+    {
+        await _mutexSubscribe.WaitAsync();
+        try
+        {
+            _closeReceived += (sender, e) => eventHandler(sender, e);
+        }
+        finally
+        {
+            _mutexSubscribe.Release();
+        }
+        return true;
+    }
+
+    /// <summary>
+    /// Subscribe to an Unhandled event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<UnhandledResponse> eventHandler)
+    {
+        await _mutexSubscribe.WaitAsync();
+        try
+        {
+            _unhandledReceived += (sender, e) => eventHandler(sender, e);
+        }
+        finally
+        {
+            _mutexSubscribe.Release();
+        }
+        return true;
+    }
+
+    /// <summary>
+    /// Subscribe to an Error event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<ErrorResponse> eventHandler)
+    {
+        await _mutexSubscribe.WaitAsync();
+        try
+        {
+            _errorReceived += (sender, e) => eventHandler(sender, e);
+        }
+        finally
+        {
+            _mutexSubscribe.Release();
+        }
+        return true;
+    }
+    #endregion
+
+    #region Send Functions
+    /// <summary>
+    /// Sends a Close message to Deepgram
+    /// </summary>
+    public virtual Task SendClose(bool nullByte = false)
+    {
+        throw new DeepgramException("Unimplemented");
+    }
+
+    /// <summary>
+    /// Sends a binary message over the WebSocket connection.
+    /// </summary>
+    /// <param name="data">The data to be sent over the WebSocket.</param>
+    public virtual void Send(byte[] data, int length = Constants.UseArrayLengthForSend) => SendBinary(data, length);
+
+    /// <summary>
+    /// This method sends a binary message over the WebSocket connection.
+    /// </summary>
+    /// <param name="data"></param>
+    public virtual void SendBinary(byte[] data, int length = Constants.UseArrayLengthForSend) =>
+        EnqueueSendMessage(new WebSocketMessage(data, WebSocketMessageType.Binary, length));
+
+    /// <summary>
+    /// This method sends a text message over the WebSocket connection.
+    /// </summary>
+    public virtual void SendMessage(byte[] data, int length = Constants.UseArrayLengthForSend) =>
+        EnqueueSendMessage(new WebSocketMessage(data, WebSocketMessageType.Text, length));
+
+    /// <summary>
+    /// This method sends a binary message over the WebSocket connection immediately without queueing.
+    /// </summary>
+    public virtual async Task SendBinaryImmediately(byte[] data, int length = Constants.UseArrayLengthForSend)
+    {
+        if (!IsConnected())
+        {
+            Log.Debug("SendBinaryImmediately", "WebSocket is not connected. Exiting...");
+            return;
+        }
+
+        await _mutexSend.WaitAsync(_cancellationTokenSource.Token);
+        try
+        {
+            Log.Verbose("SendBinaryImmediately", "Sending binary message immediately...");
+            if (length == Constants.UseArrayLengthForSend)
+            {
+                length = data.Length;
+            }
+            await _clientWebSocket.SendAsync(new ArraySegment<byte>(data, 0, length), WebSocketMessageType.Binary, true, _cancellationTokenSource.Token)
+                .ConfigureAwait(false);
+        }
+        finally
+        {
+            _mutexSend.Release();
+        }
+    }
+
+    /// <summary>
+    /// This method sends a text message over the WebSocket connection immediately without queueing.
+    /// </summary>
+    public virtual async Task SendMessageImmediately(byte[] data, int length = Constants.UseArrayLengthForSend)
+    {
+        if (!IsConnected())
+        {
+            Log.Debug("SendBinaryImmediately", "WebSocket is not connected. Exiting...");
+            return;
+        }
+
+        await _mutexSend.WaitAsync(_cancellationTokenSource.Token);
+        try
+        {
+            Log.Verbose("SendMessageImmediately", "Sending text message immediately...");
+            if (length == Constants.UseArrayLengthForSend)
+            {
+                length = data.Length;
+            }
+            await _clientWebSocket.SendAsync(new ArraySegment<byte>(data, 0, length), WebSocketMessageType.Text, true, _cancellationTokenSource.Token)
+                .ConfigureAwait(false);
+        }
+        finally
+        {
+            _mutexSend.Release();
+        }
+    }
+    #endregion
+
+    internal void EnqueueSendMessage(WebSocketMessage message)
+    {
+        try
+        {
+            _sendChannel.Writer.TryWrite(message);
+        }
+        catch (Exception ex)
+        {
+            Log.Error("EnqueueSendMessage", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("EnqueueSendMessage", $"Exception: {ex}");
+        }
+    }
+
+    internal async Task ProcessSendQueue()
+    {
+        Log.Verbose("AbstractWebSocketClient.ProcessSendQueue", "ENTER");
+
+        if (_clientWebSocket == null)
+        {
+            var exStr = "Attempting to start a sender queue when the WebSocket has been disposed is not allowed.";
+            Log.Error("EnqueueSendMessage", exStr);
+            Log.Verbose("ProcessSendQueue", "LEAVE");
+
+            throw new InvalidOperationException(exStr);
+        }
+
+        try
+        {
+            while (await _sendChannel.Reader.WaitToReadAsync(_cancellationTokenSource.Token))
+            {
+                if (_cancellationTokenSource.Token.IsCancellationRequested)
+                {
+                    Log.Information("ProcessSendQueue", "ProcessSendQueue cancelled");
+                    break;
+                }
+                if (!IsConnected())
+                {
+                    Log.Debug("ProcessSendQueue", "WebSocket is not connected. Exiting...");
+                    break;
+                }
+
+                Log.Verbose("ProcessSendQueue", "Reading message off queue...");
+                while (_sendChannel.Reader.TryRead(out var message))
+                {
+                    // TODO: Add logging for message capturing for possible playback
+                    Log.Verbose("ProcessSendQueue", "Sending message...");
+                    await _mutexSend.WaitAsync(_cancellationTokenSource.Token);
+                    try
+                    {
+                        await _clientWebSocket.SendAsync(message.Message, message.MessageType, true, _cancellationTokenSource.Token)
+                            .ConfigureAwait(false);
+                    }
+                    finally
+                    {
+                        _mutexSend.Release();
+                    }
+                }
+            }
+
+            Log.Verbose("ProcessSendQueue", "Exit");
+            Log.Verbose("AbstractWebSocketClient.ProcessSendQueue", "LEAVE");
+        }
+        catch (OperationCanceledException ex)
+        {
+            Log.Debug("ProcessSendQueue", "SendThread cancelled.");
+            Log.Verbose("ProcessSendQueue", $"SendThread cancelled. Info: {ex}");
+            Log.Verbose("AbstractWebSocketClient.ProcessSendQueue", "LEAVE");
+        }
+        catch (Exception ex)
+        {
+            Log.Error("ProcessSendQueue", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("ProcessSendQueue", $"Exception: {ex}");
+            Log.Verbose("AbstractWebSocketClient.ProcessSendQueue", "LEAVE");
+        }
+    }
+
+    internal async Task ProcessReceiveQueue()
+    {
+        Log.Verbose("AbstractWebSocketClient.ProcessReceiveQueue", "ENTER");
+
+        while (_clientWebSocket?.State == WebSocketState.Open)
+        {
+            try
+            {
+                if (_cancellationTokenSource.Token.IsCancellationRequested)
+                {
+                    Log.Information("ProcessReceiveQueue", "ReceiveThread cancelled");
+                    await Stop();
+                    Log.Verbose("ProcessReceiveQueue", "LEAVE");
+                    return;
+                }
+                if (!IsConnected())
+                {
+                    Log.Debug("ProcessReceiveQueue", "WebSocket is not connected. Exiting...");
+                    return;
+                }
+
+                var buffer = new ArraySegment<byte>(new byte[Constants.BufferSize]);
+                WebSocketReceiveResult result;
+
+                using (var ms = new MemoryStream())
+                {
+                    do
+                    {
+                        // get the result of the receive operation
+                        result = await _clientWebSocket.ReceiveAsync(buffer, _cancellationTokenSource.Token);
+
+                        ms.Write(
+                            buffer.Array ?? throw new InvalidOperationException("buffer cannot be null"),
+                            buffer.Offset,
+                            result.Count
+                            );
+                    } while (!result.EndOfMessage);
+
+                    if (result.MessageType != WebSocketMessageType.Close)
+                    {
+                        Log.Verbose("ProcessReceiveQueue", $"Received message: {result} / {ms}");
+                        ProcessDataReceived(result, ms);
+                    }
+                }
+
+                if (result.MessageType == WebSocketMessageType.Close)
+                {
+                    Log.Information("ProcessReceiveQueue", "Received WebSocket Close. Trigger cancel...");
+                    await Stop();
+                    Log.Verbose("ProcessReceiveQueue", "LEAVE");
+                    return;
+                }
+            }
+            catch (TaskCanceledException ex)
+            {
+                Log.Debug("ProcessReceiveQueue", "ReceiveThread cancelled.");
+                Log.Verbose("ProcessReceiveQueue", $"ReceiveThread cancelled. Info: {ex}");
+                Log.Verbose("AbstractWebSocketClient.ProcessReceiveQueue", "LEAVE");
+            }
+            catch (Exception ex)
+            {
+                Log.Error("ProcessReceiveQueue", $"{ex.GetType()} thrown {ex.Message}");
+                Log.Verbose("ProcessReceiveQueue", $"Exception: {ex}");
+                Log.Verbose("AbstractWebSocketClient.ProcessReceiveQueue", "LEAVE");
+            }
+        }
+    }
+
+    internal virtual void ProcessDataReceived(WebSocketReceiveResult result, MemoryStream ms)
+    {
+        Log.Verbose("AbstractWebSocketClient.ProcessDataReceived", "ENTER");
+
+        ms.Seek(0, SeekOrigin.Begin);
+
+        if (result.MessageType == WebSocketMessageType.Binary)
+        {
+            ProcessBinaryMessage(result, ms);
+        }
+        else
+        {
+            ProcessTextMessage(result, ms);
+        }
+    }
+
+    internal virtual void ProcessBinaryMessage(WebSocketReceiveResult result, MemoryStream ms)
+    {
+        throw new DeepgramException("Unimplemented");
+    }
+
+    internal virtual void ProcessTextMessage(WebSocketReceiveResult result, MemoryStream ms)
+    {
+        Log.Verbose("AbstractWebSocketClient.ProcessTextMessage", "ENTER");
+
+        ms.Seek(0, SeekOrigin.Begin);
+
+        var response = Encoding.UTF8.GetString(ms.ToArray());
+        if (response == null)
+        {
+            Log.Warning("ProcessTextMessage", "Response is null");
+            Log.Verbose("AbstractWebSocketClient.ProcessTextMessage", "LEAVE");
+            return;
+        }
+
+        try
+        {
+            Log.Verbose("ProcessTextMessage", $"raw response: {response}");
+            var data = JsonDocument.Parse(response);
+            var val = Enum.Parse(typeof(WebSocketType), data.RootElement.GetProperty("type").GetString()!);
+
+            Log.Verbose("ProcessTextMessage", $"Type: {val}");
+
+            switch (val)
+            {
+                case WebSocketType.Open:
+                    var openResponse = data.Deserialize<OpenResponse>();
+                    if (_openReceived == null)
+                    {
+                        Log.Debug("ProcessTextMessage", "_openReceived has no listeners");
+                        Log.Verbose("ProcessTextMessage", "LEAVE");
+                        return;
+                    }
+                    if (openResponse == null)
+                    {
+                        Log.Warning("ProcessTextMessage", "OpenResponse is invalid");
+                        Log.Verbose("ProcessTextMessage", "LEAVE");
+                        return;
+                    }
+
+                    Log.Debug("ProcessTextMessage", $"Invoking OpenResponse. event: {openResponse}");
+                    InvokeParallel(_openReceived, openResponse);
+                    break;
+                case WebSocketType.Error:
+                    var errorResponse = data.Deserialize<ErrorResponse>();
+                    if (_errorReceived == null)
+                    {
+                        Log.Debug("ProcessTextMessage", "_errorReceived has no listeners");
+                        Log.Verbose("ProcessTextMessage", "LEAVE");
+                        return;
+                    }
+                    if (errorResponse == null)
+                    {
+                        Log.Warning("ProcessTextMessage", "ErrorResponse is invalid");
+                        Log.Verbose("ProcessTextMessage", "LEAVE");
+                        return;
+                    }
+
+                    Log.Debug("ProcessTextMessage", $"Invoking ErrorResponse. event: {errorResponse}");
+                    InvokeParallel(_errorReceived, errorResponse);
+                    break;
+                default:
+                    if (_unhandledReceived == null)
+                    {
+                        Log.Debug("ProcessTextMessage", "_unhandledReceived has no listeners");
+                        Log.Verbose("ProcessTextMessage", "LEAVE");
+                        return;
+                    }
+
+                    var unhandledResponse = new UnhandledResponse();
+                    unhandledResponse.Type = WebSocketType.Unhandled;
+                    unhandledResponse.Raw = response;
+
+                    Log.Debug("ProcessTextMessage", $"Invoking UnhandledResponse. event: {unhandledResponse}");
+                    InvokeParallel(_unhandledReceived, unhandledResponse);
+                    break;
+            }
+
+            Log.Debug("ProcessTextMessage", "Succeeded");
+            Log.Verbose("AbstractWebSocketClient.ProcessTextMessage", "LEAVE");
+        }
+        catch (JsonException ex)
+        {
+            Log.Error("ProcessTextMessage", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("ProcessTextMessage", $"Exception: {ex}");
+            Log.Verbose("AbstractWebSocketClient.ProcessTextMessage", "LEAVE");
+        }
+        catch (Exception ex)
+        {
+            Log.Error("ProcessTextMessage", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("ProcessTextMessage", $"Exception: {ex}");
+            Log.Verbose("AbstractWebSocketClient.ProcessTextMessage", "LEAVE");
+        }
+    }
+
+    /// <summary>
+    /// Closes the Web Socket connection to the Deepgram API
+    /// </summary>
+    /// <returns>The task object representing the asynchronous operation.</returns>
+    public async Task<bool> Stop(CancellationTokenSource? cancelToken = null, bool nullByte = false)
+    {
+        Log.Verbose("AbstractWebSocketClient.Stop", "ENTER");
+
+        // client is already disposed
+        if (_clientWebSocket == null)
+        {
+            Log.Information("Stop", "Client has already been disposed");
+            Log.Verbose("AbstractWebSocketClient.Stop", "LEAVE");
+            return true;
+        }
+
+        if (cancelToken == null)
+        {
+            Log.Information("Stop", "Using default disconnect cancellation token");
+            cancelToken = new CancellationTokenSource(Constants.DefaultDisconnectTimeout);
+        }
+
+        try
+        {
+            // if websocket is open, send a close message
+            if (_clientWebSocket!.State == WebSocketState.Open)
+            {
+                Log.Debug("Stop", "Sending Close message...");
+                await SendClose(nullByte);
+            }
+
+            // small delay to wait for any final transcription
+            await Task.Delay(100, cancelToken.Token).ConfigureAwait(false);
+
+            // send a CloseResponse event
+            if (_closeReceived != null)
+            {
+                Log.Debug("Stop", "Sending CloseResponse event...");
+                var data = new CloseResponse();
+                data.Type = WebSocketType.Close;
+                InvokeParallel(_closeReceived, data);
+            }
+
+            // attempt to stop the connection
+            if (_clientWebSocket!.State != WebSocketState.Closed && _clientWebSocket!.State != WebSocketState.Aborted)
+            {
+                Log.Debug("Stop", "Closing WebSocket connection...");
+                await _clientWebSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancelToken.Token)
+                    .ConfigureAwait(false);
+            }
+
+            // clean up internal token
+            if (_cancellationTokenSource != null)
+            {
+                Log.Debug("Stop", "Disposing internal token...");
+                _cancellationTokenSource.Dispose();
+                _cancellationTokenSource = null;
+            }
+
+            // release the socket
+            Log.Debug("Stop", "Disposing WebSocket socket...");
+            _clientWebSocket = null;
+
+            Log.Debug("Stop", "Succeeded");
+            Log.Verbose("AbstractWebSocketClient.Stop", "LEAVE");
+
+            return true;
+        }
+        catch (TaskCanceledException ex)
+        {
+            Log.Debug("Stop", "Stop cancelled.");
+            Log.Verbose("Stop", $"Stop cancelled. Info: {ex}");
+            Log.Verbose("AbstractWebSocketClient.Stop", "LEAVE");
+
+            return true;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("Stop", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("Stop", $"Exception: {ex}");
+            Log.Verbose("AbstractWebSocketClient.Stop", "LEAVE");
+            throw;
+        }
+    }
+
+    #region Helpers
+    /// <summary>
+    /// Retrieves the connection state of the WebSocket
+    /// </summary>
+    /// <returns>Returns the connection state of the WebSocket</returns>
+    public WebSocketState State()
+    {
+        if (_clientWebSocket == null)
+        {
+            return WebSocketState.None;
+        }
+        Log.Debug("State", $"WebSocket State: {_clientWebSocket.State}");
+        return _clientWebSocket.State;
+    }
+
+    /// <summary>
+    /// Indicates whether the WebSocket is connected
+    /// </summary> 
+    /// <returns>Returns true if the WebSocket is connected</returns>
+    public bool IsConnected() {
+        if (_clientWebSocket == null)
+        {
+            return false;
+        }
+
+        Log.Debug("State", $"WebSocket State: {_clientWebSocket.State}");
+        return _clientWebSocket.State == WebSocketState.Open;
+    }
+
+    /// <summary>
+    /// Handle channel options
+    /// </summary> 
+    internal readonly Channel<WebSocketMessage> _sendChannel = System.Threading.Channels.Channel
+       .CreateUnbounded<WebSocketMessage>(new UnboundedChannelOptions { SingleReader = true, SingleWriter = true, });
+
+    internal void InvokeParallel<T>(EventHandler<T>? eventHandler, T e)
+    {
+        if (eventHandler != null)
+        {
+            try
+            {
+                Parallel.ForEach(
+                    eventHandler.GetInvocationList().Cast<EventHandler<T>>(),
+                    (handler) =>
+                        handler(null, e));
+            }
+            catch (AggregateException ae)
+            {
+                Log.Error("InvokeParallel", $"AggregateException occurred in one or more event handlers: {ae}");
+            }
+            catch (Exception ex)
+            {
+                Log.Error("InvokeParallel", $"Exception occurred in event handler: {ex}");
+            }
+        }
+    }
+    #endregion
+
+    #region Dispose
+    /// <summary>
+    /// Disposes of the resources used by the client
+    /// </summary> 
+    public void Dispose()
+    {
+        if (_clientWebSocket == null)
+        {
+            return;
+        }
+          
+        if (_cancellationTokenSource != null)
+        {
+            if (!_cancellationTokenSource.Token.IsCancellationRequested)
+            {
+                _cancellationTokenSource.Cancel();
+            }
+            _cancellationTokenSource.Dispose();
+            _cancellationTokenSource = null;
+        }
+
+        if (_sendChannel != null)
+        {
+            _sendChannel.Writer.Complete();
+        }
+
+        if (_clientWebSocket != null)
+        {
+            _clientWebSocket.Dispose();
+            _clientWebSocket = null;
+        }
+
+        GC.SuppressFinalize(this);
+    }
+    #endregion
+}
diff --git a/Deepgram/Abstractions/v2/Constants.cs b/Deepgram/Abstractions/v2/Constants.cs
new file mode 100644
index 00000000..50d3a72f
--- /dev/null
+++ b/Deepgram/Abstractions/v2/Constants.cs
@@ -0,0 +1,24 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Abstractions.v2;
+
+/// <summary> 
+/// Defaults for the REST and WS AbstractClient
+/// </summary> 
+public static class Constants
+{
+    // For REST
+    public const int OneSecond = 1000;
+    public const int OneMinute = 60 * OneSecond;
+    public const int DefaultRESTTimeout = 30 * OneSecond;
+
+    // For WS
+    public const int BufferSize = 1024 * 16;
+    public const int UseArrayLengthForSend = -1;
+
+    public const int DefaultConnectTimeout = 5000;
+    public const int DefaultDisconnectTimeout = 5000;
+}
+
diff --git a/Deepgram/Abstractions/v2/LocalFileWithMetadata.cs b/Deepgram/Abstractions/v2/LocalFileWithMetadata.cs
new file mode 100644
index 00000000..5d3a9524
--- /dev/null
+++ b/Deepgram/Abstractions/v2/LocalFileWithMetadata.cs
@@ -0,0 +1,33 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Abstractions.v2;
+
+/// <summary>
+/// LocalFileWithMetadata is a class that represents a file with metadata.
+/// </summary>
+public class LocalFileWithMetadata :  IDisposable
+{
+    /// <summary>
+    /// Gets or sets the metadata associated with the file content.
+    /// </summary>
+    public Dictionary<string, string> Metadata { get; set; }
+
+    /// <summary>
+    /// Gets or sets the file content as a MemoryStream.
+    /// The caller is responsible for disposing of this stream when no longer needed.
+    /// </summary>
+    /// <remarks>
+    /// This property should be properly disposed of to prevent memory leaks.
+    /// </remarks>
+    public MemoryStream Content { get; set; }
+
+    /// <summary>
+    /// Releases the resources used by the Content stream.
+    /// </summary>
+    public void Dispose()
+    {
+        Content?.Dispose();
+    }
+}
diff --git a/Deepgram/Abstractions/v2/NoopSchema.cs b/Deepgram/Abstractions/v2/NoopSchema.cs
new file mode 100644
index 00000000..d56de536
--- /dev/null
+++ b/Deepgram/Abstractions/v2/NoopSchema.cs
@@ -0,0 +1,13 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Abstractions.v2;
+
+/// <summary>
+/// Just a NoopSchema where you dont need to marshall JSON into a Async function.
+/// </summary>
+public class NoopSchema
+{
+}
+
diff --git a/Deepgram/Abstractions/v2/Utilities.cs b/Deepgram/Abstractions/v2/Utilities.cs
new file mode 100644
index 00000000..65f134d1
--- /dev/null
+++ b/Deepgram/Abstractions/v2/Utilities.cs
@@ -0,0 +1,61 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Abstractions.v2;
+
+/// <summary>
+/// Helper funcitons for HttpRequests
+/// </summary>
+internal static class HttpRequestUtil
+{
+    public const string DEFAULT_CONTENT_TYPE = "application/json";
+
+    static readonly JsonSerializerOptions _jsonSerializerOptions = new()
+    {
+        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+        NumberHandling = JsonNumberHandling.AllowReadingFromString,
+    };
+
+    /// <summary>
+    /// Create the body payload of a HttpRequestMessage
+    /// </summary>
+    /// <typeparam name="T">Type of the body to be sent</typeparam>
+    /// <param name="body">instance value for the body</param>
+    /// <param name="contentType">What type of content is being sent default is : application/json</param>
+    /// <returns></returns>
+    internal static StringContent CreatePayload<T>(T body)
+    {
+        var serialized = JsonSerializer.Serialize(body, _jsonSerializerOptions);
+        return new(serialized, Encoding.UTF8, DEFAULT_CONTENT_TYPE);
+    }
+
+
+    /// <summary>
+    /// Create the stream payload of a HttpRequestMessage
+    /// </summary>
+    /// <param name="body">of type stream</param>
+    /// <returns>HttpContent</returns>
+    internal static HttpContent CreateStreamPayload(Stream body)
+    {
+        body.Seek(0, SeekOrigin.Begin);
+        HttpContent httpContent = new StreamContent(body);
+        httpContent.Headers.Add("Content-Length", body.Length.ToString());
+        return httpContent;
+    }
+
+
+    /// <summary>
+    /// method that deserializes DeepgramResponse and performs null checks on values
+    /// </summary>
+    /// <typeparam name="TResponse">Class Type of expected response</typeparam>
+    /// <param name="httpResponseMessage">Http Response to be deserialized</param>       
+    /// <returns>instance of TResponse or a Exception</returns>
+    internal static async Task<TResponse> DeserializeAsync<TResponse>(HttpResponseMessage httpResponseMessage)
+    {
+        var content = await httpResponseMessage.Content.ReadAsStringAsync();
+        var deepgramResponse = JsonSerializer.Deserialize<TResponse>(content);
+        return deepgramResponse;
+    }
+
+}
diff --git a/Deepgram/Abstractions/v2/WebSocketMessage.cs b/Deepgram/Abstractions/v2/WebSocketMessage.cs
new file mode 100644
index 00000000..31a5182b
--- /dev/null
+++ b/Deepgram/Abstractions/v2/WebSocketMessage.cs
@@ -0,0 +1,32 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Abstractions.v2;
+
+internal readonly struct WebSocketMessage
+{
+    public WebSocketMessage(byte[] message, WebSocketMessageType type)
+        : this(message, type, Constants.UseArrayLengthForSend)
+    {
+    }
+
+    public WebSocketMessage(byte[] message, WebSocketMessageType type, int length)
+    {
+        if (length != Constants.UseArrayLengthForSend && length <= message.Length && length > 0)
+        {
+            Message = new ArraySegment<byte>(message, 0, length);
+        }
+        else
+        {
+            Message = new ArraySegment<byte>(message, 0, message.Length);
+        }
+        MessageType = type;
+    }
+
+    public int Length => Message.Count;
+
+    public ArraySegment<byte> Message { get; }
+
+    public WebSocketMessageType MessageType { get; }
+}
diff --git a/Deepgram/ClientFactory.cs b/Deepgram/ClientFactory.cs
index 7fed455a..17e2f7d6 100644
--- a/Deepgram/ClientFactory.cs
+++ b/Deepgram/ClientFactory.cs
@@ -3,7 +3,13 @@
 // SPDX-License-Identifier: MIT
 
 using Deepgram.Models.Authenticate.v1;
-using Deepgram.Clients.Interfaces.v1;
+using V1 = Deepgram.Clients.Interfaces.v1;
+using V2 = Deepgram.Clients.Interfaces.v2;
+
+using ListenV1 = Deepgram.Clients.Listen.v1.WebSocket;
+using ListenV2 = Deepgram.Clients.Listen.v2.WebSocket;
+using SpeakV1 = Deepgram.Clients.Speak.v1.WebSocket;
+using SpeakV2 = Deepgram.Clients.Speak.v2.WebSocket;
 
 namespace Deepgram;
 
@@ -16,11 +22,20 @@ public static class ClientFactory
     /// <param name="options"></param>
     /// <param name="httpId"></param>
     /// <returns></returns>
-    public static IAnalyzeClient CreateAnalyzeClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
+    public static V1.IAnalyzeClient CreateAnalyzeClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
     {
         return new AnalyzeClient(apiKey, options, httpId);
     }
 
+    /// <summary>
+    /// This method allows you to create an AnalyzeClient with a specific version of the client.
+    /// </summary>
+    public static object CreateAnalyzeClient(int version, string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
+    {
+        // Currently only a single version of the AnalyzeClient exists
+        return new AnalyzeClient(apiKey, options, httpId);
+    }
+
     /// <summary>
     // *********** WARNING ***********
     // This function creates a LiveClient for the Deepgram API
@@ -32,22 +47,41 @@ public static IAnalyzeClient CreateAnalyzeClient(string apiKey = "", DeepgramHtt
     // *********** WARNING ***********
     /// </summary>
     [Obsolete("Please use CreateListenWebSocketClient instead", false)]
-    public static ILiveClient CreateLiveClient(string apiKey = "", DeepgramWsClientOptions? options = null)
+    public static V1.ILiveClient CreateLiveClient(string apiKey = "", DeepgramWsClientOptions? options = null)
     {
         return new LiveClient(apiKey, options);
     }
 
     /// <summary>
-    /// Create a new ListenWebSocketClient
+    /// Create a new ListenWebSocketClient using the latest version
     /// </summary>
     /// <param name="apiKey"></param>
     /// <param name="options"></param>
     /// <returns></returns>
-    public static IListenWebSocketClient CreateListenWebSocketClient(string apiKey = "", DeepgramWsClientOptions? options = null)
+    public static V2.IListenWebSocketClient CreateListenWebSocketClient(string apiKey = "", DeepgramWsClientOptions? options = null)
     {
         return new ListenWebSocketClient(apiKey, options);
     }
 
+    /// <summary>
+    /// This method allows you to create an AnalyzeClient with a specific version of the client.
+    /// </summary>
+    public static object CreateListenWebSocketClient(int version, string apiKey = "", DeepgramWsClientOptions? options = null)
+    {
+        // at some point this needs to be changed to use reflection to get the type of the client
+        switch(version)
+        {
+            case 1:
+                Log.Information("ClientFactory", $"Version 1 of the ListenWebSocketClient is being deprecated in the next major version.");
+                Log.Information("ClientFactory", $"Transition to the latest version at your earliest convenience.");
+                return new ListenV1.Client(apiKey, options);
+            case 2:
+                return new ListenV2.Client(apiKey, options);
+            default:
+                throw new ArgumentException("Invalid version", nameof(version));
+        }
+    }
+
     /// <summary>
     /// Create a new ManageClient
     /// </summary>
@@ -55,8 +89,17 @@ public static IListenWebSocketClient CreateListenWebSocketClient(string apiKey =
     /// <param name="options"></param>
     /// <param name="httpId"></param>
     /// <returns></returns>
-    public static IManageClient CreateManageClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
+    public static V1.IManageClient CreateManageClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
+    {
+        return new ManageClient(apiKey, options, httpId);
+    }
+
+    /// <summary>
+    /// This method allows you to create an ManageClient with a specific version of the client.
+    /// </summary>
+    public static object CreateManageClient(int version, string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
     {
+        // Currently only a single version of the ManageClient exists
         return new ManageClient(apiKey, options, httpId);
     }
 
@@ -71,7 +114,7 @@ public static IManageClient CreateManageClient(string apiKey = "", DeepgramHttpC
     // *********** WARNING ***********
     /// </summary>
     [Obsolete("Please use CreateSelfHostedClient instead", false)]
-    public static IOnPremClient CreateOnPremClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
+    public static V1.IOnPremClient CreateOnPremClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
     {
         return new OnPremClient(apiKey, options, httpId);
     }
@@ -83,11 +126,20 @@ public static IOnPremClient CreateOnPremClient(string apiKey = "", DeepgramHttpC
     /// <param name="options"></param>
     /// <param name="httpId"></param>
     /// <returns></returns>
-    public static ISelfHostedClient CreateSelfHostedClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
+    public static V1.ISelfHostedClient CreateSelfHostedClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
     {
         return new SelfHostedClient(apiKey, options, httpId);
     }
 
+    /// <summary>
+    /// This method allows you to create an SelfHostedClient with a specific version of the client.
+    /// </summary>
+    public static object CreateSelfHostedClient(int version, string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
+    {
+        // Currently only a single version of the SelfHostedClient exists
+        return new SelfHostedClient(apiKey, options, httpId);
+    }
+
     /// <summary>
     // *********** WARNING ***********
     // This function creates a PreRecordedClient for the Deepgram API
@@ -99,7 +151,7 @@ public static ISelfHostedClient CreateSelfHostedClient(string apiKey = "", Deepg
     // *********** WARNING ***********
     /// </summary>
     [Obsolete("Please use CreateListenRESTClient instead", false)]
-    public static IPreRecordedClient CreatePreRecordedClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
+    public static V1.IPreRecordedClient CreatePreRecordedClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
     {
         return new PreRecordedClient(apiKey, options, httpId);
     }
@@ -111,8 +163,17 @@ public static IPreRecordedClient CreatePreRecordedClient(string apiKey = "", Dee
     /// <param name="options"></param>
     /// <param name="httpId"></param>
     /// <returns></returns>
-    public static IListenRESTClient CreateListenRESTClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
+    public static V1.IListenRESTClient CreateListenRESTClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
+    {
+        return new ListenRESTClient(apiKey, options, httpId);
+    }
+
+    /// <summary>
+    /// This method allows you to create an ListenRESTClient with a specific version of the client.
+    /// </summary>
+    public static object CreateListenRESTClient(int version, string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
     {
+        // Currently only a single version of the ListenRESTClient exists
         return new ListenRESTClient(apiKey, options, httpId);
     }
 
@@ -127,7 +188,7 @@ public static IListenRESTClient CreateListenRESTClient(string apiKey = "", Deepg
     // *********** WARNING ***********
     /// </summary>
     [Obsolete("Please use CreateSpeakRESTClient instead", false)]
-    public static ISpeakClient CreateSpeakClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
+    public static V1.ISpeakClient CreateSpeakClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
     {
         return new SpeakClient(apiKey, options, httpId);
     }
@@ -139,8 +200,17 @@ public static ISpeakClient CreateSpeakClient(string apiKey = "", DeepgramHttpCli
     /// <param name="options"></param>
     /// <param name="httpId"></param>
     /// <returns></returns>
-    public static ISpeakRESTClient CreateSpeakRESTClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
+    public static V1.ISpeakRESTClient CreateSpeakRESTClient(string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
+    {
+        return new SpeakRESTClient(apiKey, options, httpId);
+    }
+
+    /// <summary>
+    /// This method allows you to create an SpeakRESTClient with a specific version of the client.
+    /// </summary>
+    public static object CreateSpeakRESTClient(int version, string apiKey = "", DeepgramHttpClientOptions? options = null, string? httpId = null)
     {
+        // Currently only a single version of the SpeakRESTClient exists
         return new SpeakRESTClient(apiKey, options, httpId);
     }
 
@@ -150,8 +220,27 @@ public static ISpeakRESTClient CreateSpeakRESTClient(string apiKey = "", Deepgra
     /// <param name="apiKey"></param>
     /// <param name="options"></param>
     /// <returns></returns>
-    public static ISpeakWebSocketClient CreateSpeakWebSocketClient(string apiKey = "", DeepgramWsClientOptions? options = null)
+    public static V2.ISpeakWebSocketClient CreateSpeakWebSocketClient(string apiKey = "", DeepgramWsClientOptions? options = null)
     {
         return new SpeakWebSocketClient(apiKey, options);
     }
+
+    /// <summary>
+    /// This method allows you to create an SpeakWebSocketClient with a specific version of the client.
+    /// </summary>
+    public static object CreateSpeakWebSocketClient(int version, string apiKey = "", DeepgramHttpClientOptions? options = null)
+    {
+        // at some point this needs to be changed to use reflection to get the type of the client
+        switch (version)
+        {
+            case 1:
+                Log.Information("ClientFactory", $"Version 1 of the ListenWebSocketClient is being deprecated in the next major version.");
+                Log.Information("ClientFactory", $"Transition to the latest version at your earliest convenience.");
+                return new SpeakV1.Client(apiKey, options);
+            case 2:
+                return new SpeakV2.Client(apiKey, options);
+            default:
+                throw new ArgumentException("Invalid version", nameof(version));
+        }
+    }
 }
diff --git a/Deepgram/Clients/Analyze/v1/Client.cs b/Deepgram/Clients/Analyze/v1/Client.cs
index e4bec72a..0dbba654 100644
--- a/Deepgram/Clients/Analyze/v1/Client.cs
+++ b/Deepgram/Clients/Analyze/v1/Client.cs
@@ -5,6 +5,7 @@
 using Deepgram.Models.Analyze.v1;
 using Deepgram.Models.Authenticate.v1;
 using Deepgram.Clients.Interfaces.v1;
+using Deepgram.Abstractions.v1;
 
 namespace Deepgram.Clients.Analyze.v1;
 
diff --git a/Deepgram/Clients/Interfaces/v1/ISpeakClient.cs b/Deepgram/Clients/Interfaces/v1/ISpeakClient.cs
index 411eb2f8..e20bb4af 100644
--- a/Deepgram/Clients/Interfaces/v1/ISpeakClient.cs
+++ b/Deepgram/Clients/Interfaces/v1/ISpeakClient.cs
@@ -5,14 +5,14 @@
 namespace Deepgram.Clients.Interfaces.v1;
 
 /// <summary>
-// *********** WARNING ***********
-// This class provides the ISpeakClient implementation for the Deepgram API
-//
-// Deprecated: This class is deprecated. Use the ISpeakRESTClient interface instead.
-// This will be removed in a future release.
-//
-// This package is frozen and no new functionality will be added.
-// *********** WARNING ***********
+/// *********** WARNING ***********
+/// This class provides the ISpeakClient implementation for the Deepgram API
+///
+/// Deprecated: This class is deprecated. Use the ISpeakRESTClient interface instead.
+/// This will be removed in a future release.
+///
+/// This package is frozen and no new functionality will be added.
+/// *********** WARNING ***********
 /// </summary>
 [Obsolete("Please use ISpeakRESTClient instead", false)]
 public interface ISpeakClient : ISpeakRESTClient
diff --git a/Deepgram/Clients/Interfaces/v1/IResponseEvent.cs b/Deepgram/Clients/Interfaces/v1/ResponseEvent.cs
similarity index 64%
rename from Deepgram/Clients/Interfaces/v1/IResponseEvent.cs
rename to Deepgram/Clients/Interfaces/v1/ResponseEvent.cs
index fbcf5daa..f48d41f7 100644
--- a/Deepgram/Clients/Interfaces/v1/IResponseEvent.cs
+++ b/Deepgram/Clients/Interfaces/v1/ResponseEvent.cs
@@ -4,8 +4,13 @@
 
 namespace Deepgram.Clients.Interfaces.v1;
 
-public class IResponseEvent<T>
+public class ResponseEvent<T>
 {
-    public T? Response { get; set; }
+    public T? Response { get; }
+
+    public ResponseEvent(T? response)
+    {
+        Response = response;
+    }
 }
 
diff --git a/Deepgram/Clients/Interfaces/v2/Constants.cs b/Deepgram/Clients/Interfaces/v2/Constants.cs
new file mode 100644
index 00000000..8b016fe7
--- /dev/null
+++ b/Deepgram/Clients/Interfaces/v2/Constants.cs
@@ -0,0 +1,15 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Clients.Interfaces.v2;
+
+/// <summary> 
+/// Headers of interest in the return values from the Deepgram Speak API.
+/// </summary> 
+public static class Constants
+{
+    // WS buffer size
+    public const int UseArrayLengthForSend = -1;
+}
+
diff --git a/Deepgram/Clients/Interfaces/v2/IAnalyzeClient.cs b/Deepgram/Clients/Interfaces/v2/IAnalyzeClient.cs
new file mode 100644
index 00000000..ce16cd02
--- /dev/null
+++ b/Deepgram/Clients/Interfaces/v2/IAnalyzeClient.cs
@@ -0,0 +1,94 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Models.Analyze.v1;
+
+namespace Deepgram.Clients.Interfaces.v2;
+
+/// <summary>
+/// Not currently being used
+/// </summary>
+public interface IAnalyzeClient
+{
+    #region NoneCallBacks
+    /// <summary>
+    ///  Analyze a file by providing a url 
+    /// </summary>
+    /// <param name="source">Url to the file that is to be analyzed <see cref="UrlSource"></param>
+    /// <param name="analyzeSchema">Options for the transcription <see cref="AnalyzeSchema"/></param>
+    /// <returns><see cref="SyncResponse"/></returns>
+    public Task<SyncResponse> AnalyzeUrl(UrlSource source, AnalyzeSchema? analyzeSchema, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    ///  Analyze by providing text 
+    /// </summary>
+    /// <param name="source">Text that is to be analyzed <see cref="TextSource"></param>
+    /// <param name="analyzeSchema">Options for the transcription <see cref="AnalyzeSchema"/></param>
+    /// <returns><see cref="SyncResponse"/></returns>
+    public Task<SyncResponse> AnalyzeText(TextSource source, AnalyzeSchema? analyzeSchema, CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Analyzes a file using the provided byte array
+    /// </summary>
+    /// <param name="source">file is the form of a byte[]</param>
+    /// <param name="analyzeSchema">Options for the transcription <see cref="AnalyzeSchema"/></param>
+    /// <returns><see cref="SyncResponse"/></returns>
+    public Task<SyncResponse> AnalyzeFile(byte[] source, AnalyzeSchema? analyzeSchema, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Analyzes a file using the provided stream
+    /// </summary>
+    /// <param name="source">file is the form of a stream <see cref="Stream"/></param>
+    /// <param name="analyzeSchema">Options for the transcription <see cref="AnalyzeSchema"/></param>
+    /// <returns><see cref="SyncResponse"/></returns>
+    public Task<SyncResponse> AnalyzeFile(Stream source, AnalyzeSchema? analyzeSchema, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+    #endregion
+
+    #region  CallBack Methods
+    /// <summary>
+    /// Analyzes a file using the provided byte array and providing a CallBack
+    /// </summary>
+    /// <param name="source">file is the form of a byte[]</param>  
+    /// <param name="callBack">CallBack url</param>    
+    /// <param name="analyzeSchema">Options for the transcription<see cref="AnalyzeSchema"></param>
+    /// <returns><see cref="AsyncResponse"/></returns>
+    public Task<AsyncResponse> AnalyzeFileCallBack(byte[] source, string? callBack, AnalyzeSchema? analyzeSchema,
+        CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Analyzes a file using the provided stream and providing a CallBack
+    /// </summary>
+    /// <param name="source">file is the form of a stream <see cref="Stream"></param>  
+    /// <param name="callBack">CallBack url</param>    
+    /// <param name="analyzeSchema">Options for the transcription<see cref="AnalyzeSchema"></param>
+    /// <returns><see cref="AsyncResponse"/></returns>
+    public Task<AsyncResponse> AnalyzeFileCallBack(Stream source, string? callBack, AnalyzeSchema? analyzeSchema,
+        CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Analyze a file by providing a url and a CallBack
+    /// </summary>
+    /// <param name="source">Url to the file that is to be analyzed <see cref="UrlSource"/></param>
+    /// <param name="callBack">CallBack url</param>    
+    /// <param name="analyzeSchema">Options for the transcription<see cref="AnalyzeSchema"></param>
+    /// <returns><see cref="AsyncResponse"/></returns>
+    public Task<AsyncResponse> AnalyzeUrlCallBack(UrlSource source, string? callBack, AnalyzeSchema? analyzeSchema,
+        CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Analyze by providing text and a CallBack
+    /// </summary>
+    /// <param name="source">Text that is to be analyzed <see cref="TextSource"/></param>
+    /// <param name="callBack">CallBack url</param>    
+    /// <param name="analyzeSchema">Options for the transcription<see cref="AnalyzeSchema"></param>
+    /// <returns><see cref="AsyncResponse"/></returns>
+    public Task<AsyncResponse> AnalyzeTextCallBack(TextSource source, string? callBack, AnalyzeSchema? analyzeSchema, CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+    #endregion
+}
diff --git a/Deepgram/Clients/Interfaces/v2/IListenRESTClient.cs b/Deepgram/Clients/Interfaces/v2/IListenRESTClient.cs
new file mode 100644
index 00000000..d76b3d29
--- /dev/null
+++ b/Deepgram/Clients/Interfaces/v2/IListenRESTClient.cs
@@ -0,0 +1,79 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Models.Listen.v1.REST;
+
+namespace Deepgram.Clients.Interfaces.v2;
+
+/// <summary>
+/// Not currently being used
+/// </summary>
+public interface IListenRESTClient
+
+{
+    #region NoneCallBacks
+    /// <summary>
+    ///  Transcribe a file by providing a url 
+    /// </summary>
+    /// <param name="source">Url to the file that is to be transcribed <see cref="UrlSource"></param>
+    /// <param name="prerecordedSchema">Options for the transcription <see cref="PreRecordedSchema"/></param>
+    /// <returns><see cref="SyncResponse"/></returns>
+    public Task<SyncResponse> TranscribeUrl(UrlSource source, PreRecordedSchema? prerecordedSchema,
+        CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Transcribes a file using the provided byte array
+    /// </summary>
+    /// <param name="source">file is the form of a byte[]</param>
+    /// <param name="prerecordedSchema">Options for the transcription <see cref="PreRecordedSchema"/></param>
+    /// <returns><see cref="SyncResponse"/></returns>
+    public Task<SyncResponse> TranscribeFile(byte[] source, PreRecordedSchema? prerecordedSchema,
+        CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Transcribes a file using the provided stream
+    /// </summary>
+    /// <param name="source">file is the form of a streasm <see cref="Stream"/></param>
+    /// <param name="prerecordedSchema">Options for the transcription <see cref="PreRecordedSchema"/></param>
+    /// <returns><see cref="SyncResponse"/></returns>
+    public Task<SyncResponse> TranscribeFile(Stream source, PreRecordedSchema? prerecordedSchema,
+        CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null);
+    #endregion
+
+    #region  CallBack Methods
+    /// <summary>
+    /// Transcribes a file using the provided byte array and providing a CallBack
+    /// </summary>
+    /// <param name="source">file is the form of a byte[]</param>  
+    /// <param name="callBack">CallBack url</param>    
+    /// <param name="prerecordedSchema">Options for the transcription<see cref="PreRecordedSchema"></param>
+    /// <returns><see cref="AsyncResponse"/></returns>
+    public Task<AsyncResponse> TranscribeFileCallBack(byte[] source, string? callBack, PreRecordedSchema? prerecordedSchema,
+        CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Transcribes a file using the provided stream and providing a CallBack
+    /// </summary>
+    /// <param name="source">file is the form of a stream <see cref="Stream"></param>  
+    /// <param name="callBack">CallBack url</param>    
+    /// <param name="prerecordedSchema">Options for the transcription<see cref="PreRecordedSchema"></param>
+    /// <returns><see cref="AsyncResponse"/></returns>
+    public Task<AsyncResponse> TranscribeFileCallBack(Stream source, string? callBack, PreRecordedSchema? prerecordedSchema,
+        CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Transcribe a file by providing a url and a CallBack
+    /// </summary>
+    /// <param name="source">Url to the file that is to be transcribed <see cref="UrlSource"/></param>
+    /// <param name="callBack">CallBack url</param>    
+    /// <param name="prerecordedSchema">Options for the transcription<see cref="PreRecordedSchema"></param>
+    /// <returns><see cref="AsyncResponse"/></returns>
+    public Task<AsyncResponse> TranscribeUrlCallBack(UrlSource source, string? callBack, PreRecordedSchema? prerecordedSchema,
+        CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null);
+    #endregion 
+}
diff --git a/Deepgram/Clients/Interfaces/v2/IListenWebSocketClient.cs b/Deepgram/Clients/Interfaces/v2/IListenWebSocketClient.cs
new file mode 100644
index 00000000..d59cce6d
--- /dev/null
+++ b/Deepgram/Clients/Interfaces/v2/IListenWebSocketClient.cs
@@ -0,0 +1,137 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Models.Listen.v2.WebSocket;
+
+namespace Deepgram.Clients.Interfaces.v2;
+
+/// <summary>
+/// Implements version 2 of the Live Client.
+/// </summary>
+public interface IListenWebSocketClient
+{
+    #region Connect and Disconnect
+    public Task<bool> Connect(LiveSchema options, CancellationTokenSource? cancelToken = null, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null);
+
+    public Task<bool> Stop(CancellationTokenSource? cancelToken = null, bool nullByte = false);
+    #endregion
+
+    #region Subscribe Event
+    /// <summary>
+    /// Subscribe to an Open event from the Deepgram API
+    /// </summary>
+    /// <param name="eventHandler"></param>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<OpenResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to a Metadata event from the Deepgram API
+    /// </summary>
+    /// <param name="eventHandler"></param>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<MetadataResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to a Results event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<ResultResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to an UtteranceEnd event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<UtteranceEndResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to a SpeechStarted event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<SpeechStartedResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to a Close event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<CloseResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to an Unhandled event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<UnhandledResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to an Error event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<ErrorResponse> eventHandler);
+    #endregion
+
+    #region Send Functions
+    /// <summary>
+    /// Sends a KeepAlive message to Deepgram
+    /// </summary>
+    public Task SendKeepAlive();
+
+    /// <summary>
+    /// Sends a Finalize message to Deepgram
+    /// </summary>
+    public Task SendFinalize();
+
+    /// <summary>
+    /// Sends a Close message to Deepgram
+    /// </summary>
+    public Task SendClose(bool nullByte = false);
+
+    /// <summary>
+    /// Sends a binary message over the WebSocket connection.
+    /// </summary>
+    /// <param name="data">The data to be sent over the WebSocket.</param>
+    public void Send(byte[] data, int length = Constants.UseArrayLengthForSend);
+
+    /// <summary>
+    /// This method sends a binary message over the WebSocket connection.
+    /// </summary>
+    /// <param name="data"></param>
+    /// <param name="length">The number of bytes from the data to send. Use `Constants.UseArrayLengthForSend` to send the entire array.</param>
+    public void SendBinary(byte[] data, int length = Constants.UseArrayLengthForSend);
+
+    /// <summary>
+    /// This method sends a text message over the WebSocket connection.
+    /// </summary>
+    /// <param name="data"></param>
+    /// <param name="length">The number of bytes from the data to send. Use `Constants.UseArrayLengthForSend` to send the entire array.</param>
+    public void SendMessage(byte[] data, int length = Constants.UseArrayLengthForSend);
+
+    /// <summary>
+    /// This method sends a binary message over the WebSocket connection immediately without queueing.
+    /// </summary>
+    /// <param name="data"></param>
+    /// <param name="length">The number of bytes from the data to send. Use `Constants.UseArrayLengthForSend` to send the entire array.</param>
+    public Task SendBinaryImmediately(byte[] data, int length = Constants.UseArrayLengthForSend);
+
+    /// <summary>
+    /// This method sends a text message over the WebSocket connection immediately without queueing.
+    /// </summary>
+    /// <param name="data"></param>
+    /// <param name="length">The number of bytes from the data to send. Use `Constants.UseArrayLengthForSend` to send the entire array.</param>
+    public Task SendMessageImmediately(byte[] data, int length = Constants.UseArrayLengthForSend);
+    #endregion
+
+    #region Helpers
+    /// <summary>
+    /// Retrieves the connection state of the WebSocket
+    /// </summary>
+    /// <returns>Returns the connection state of the WebSocket</returns>
+    public WebSocketState State();
+
+    /// <summary>
+    /// Indicates whether the WebSocket is connected
+    /// </summary> 
+    /// <returns>Returns true if the WebSocket is connected</returns>
+    public bool IsConnected();
+    #endregion
+}
diff --git a/Deepgram/Clients/Interfaces/v2/ILiveClient.cs b/Deepgram/Clients/Interfaces/v2/ILiveClient.cs
new file mode 100644
index 00000000..d9d574c1
--- /dev/null
+++ b/Deepgram/Clients/Interfaces/v2/ILiveClient.cs
@@ -0,0 +1,73 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Models.Live.v1;
+
+namespace Deepgram.Clients.Interfaces.v2;
+
+/// <summary>
+// *********** WARNING ***********
+// This is the ILiveClient interface
+//
+// Deprecated: This class is deprecated. Use the `IListenWebSocketClient` function instead.
+// This will be removed in a future release.
+//
+// This class is frozen and no new functionality will be added.
+// *********** WARNING ***********
+/// </summary>
+[Obsolete("Please use IListenWebSocketClient instead", false)]
+public interface ILiveClient : IListenWebSocketClient
+{
+    #region Subscribe Event
+    /// <summary>
+    /// Subscribe to an Open event from the Deepgram API
+    /// </summary>
+    /// <param name="eventHandler"></param>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<OpenResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to a Metadata event from the Deepgram API
+    /// </summary>
+    /// <param name="eventHandler"></param>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<MetadataResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to a Results event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<ResultResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to an UtteranceEnd event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<UtteranceEndResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to a SpeechStarted event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<SpeechStartedResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to a Close event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<CloseResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to an Unhandled event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<UnhandledResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to an Error event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<ErrorResponse> eventHandler);
+    #endregion
+}
diff --git a/Deepgram/Clients/Interfaces/v2/IManageClient.cs b/Deepgram/Clients/Interfaces/v2/IManageClient.cs
new file mode 100644
index 00000000..c40f1bb2
--- /dev/null
+++ b/Deepgram/Clients/Interfaces/v2/IManageClient.cs
@@ -0,0 +1,251 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Models.Manage.v1;
+
+namespace Deepgram.Clients.Interfaces.v2;
+
+/// <summary>
+/// Not currently being used
+/// </summary>
+public interface IManageClient
+{
+    #region Projects
+    /// <summary>
+    /// Gets projects associated to ApiKey 
+    /// </summary>
+    /// <returns><see cref="ProjectsResponse"/></returns>
+    public Task<ProjectsResponse> GetProjects(CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Gets project associated with project Id
+    /// </summary>
+    /// <param name="projectId">Id of Project</param>
+    /// <returns><see cref="ProjectResponse"/></returns>
+    public Task<ProjectResponse> GetProject(string projectId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Update a project associated with the projectID
+    /// </summary>
+    /// <param name="projectId">ID of project</param>
+    /// <param name="updateProjectSchema"><see cref="ProjectSchema"/> for project</param>
+    /// <returns><see cref="MessageResponse"/></returns>
+    // USES PATCH
+    public Task<MessageResponse> UpdateProject(string projectId, ProjectSchema updateProjectSchema, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Deletes a project, no response will be returned
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    // No response expected
+    public Task<MessageResponse> DeleteProject(string projectId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// leave project associated with the project Id
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <returns><see cref="MessageResponse"/></returns>
+    public Task<MessageResponse> LeaveProject(string projectId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Get all models associated with the project Id
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <returns><see cref="ModelsResponse"/></returns>
+    public Task<ModelsResponse> GetProjectModels(string projectId, ModelSchema? modelSchema = null, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Get a specific model associated with the project Id
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="modelId">Id of model</param>
+    /// <returns><see cref="ModelResponse"/></returns>
+    public Task<ModelResponse> GetProjectModel(string projectId, string modelId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+    #endregion
+
+    #region Models
+    /// <summary>
+    /// Gets models available in Deepgram
+    /// </summary>
+    /// <returns><see cref="ModelsResponse"/></returns>
+    public Task<ModelsResponse> GetModels(ModelSchema? modelSchema = null, CancellationTokenSource? cancellationToken = default,
+    Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Gets a specific model within Deepgram
+    /// </summary>
+    /// <param name="modelId">ID of model</param>
+    /// <returns><see cref="ModelResponse"/></returns>
+    public Task<ModelResponse> GetModel(string modelId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+    #endregion
+
+    #region ProjectKeys
+    /// <summary>
+    /// Get the keys associated with the project
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <returns><see cref="KeysResponse"/></returns>
+    public Task<KeysResponse> GetKeys(string projectId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Get details of key associated with the key ID
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="keyId">Id of key</param>
+    /// <returns><see cref="KeyScopeResponse"/></returns>
+    public Task<KeyScopeResponse> GetKey(string projectId, string keyId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Create a key in the associated project
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="keySchema"><see cref="KeySchema"/> for the key to be created</param>
+    /// <returns><see cref="KeyResponse"/></returns>
+    public Task<KeyResponse> CreateKey(string projectId, KeySchema keySchema, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Remove key from project, No response returned
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="keyId">Id of key</param>
+    // Nothing being returned
+    public Task<MessageResponse> DeleteKey(string projectId, string keyId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+    #endregion
+
+    #region ProjectInvites
+    /// <summary>
+    /// Get any invites that are associated with project
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <returns><see cref="InvitesResponse"/></returns>
+    public Task<InvitesResponse> GetInvites(string projectId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Delete a project invite that has been sent
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="email">email of the invite to be removed</param>
+    //no response expected
+    public Task<MessageResponse> DeleteInvite(string projectId, string email, CancellationTokenSource? cancellationToken = default,
+    Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Send a invite to the associated project
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="inviteSchema"><see cref="InviteSchema"/> for a invite to project</param>
+    /// <returns><see cref="MessageResponse"/></returns>
+    public Task<MessageResponse> SendInvite(string projectId, InviteSchema inviteSchema, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+    #endregion
+
+    #region Members
+    /// <summary>
+    /// Get the members associated with the project
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <returns><see cref="MembersResponse"/></returns>
+    public Task<MembersResponse> GetMembers(string projectId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Get the scopes associated with member
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="memberId">Id of member</param>
+    /// <returns><see cref="MemberScopesResponse"/></returns>
+    public Task<MemberScopesResponse> GetMemberScopes(string projectId, string memberId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Update the scopes fot the member
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="memberId">Id of member</param>
+    /// <param name="scopeSchema">Updates scope options for member<see cref="MemberScopeSchema"/></param>
+    /// <returns><see cref="MessageResponse"/></returns>  
+    public Task<MessageResponse> UpdateMemberScope(string projectId, string memberId, MemberScopeSchema scopeSchema,
+        CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Remove member from project, there is no response
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="memberId">Id of member</param>   
+    //No response expected
+    public Task<MessageResponse> RemoveMember(string projectId, string memberId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+    #endregion
+
+    #region Usage
+    /// <summary>
+    /// Get usage request  associated with the project
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="UsageRequestsSchema">Project usage request options<see cref="UsageRequestsSchema"/>  </param>
+    /// <returns><see cref="UsageRequestsResponse"/></returns>
+    public Task<UsageRequestsResponse> GetUsageRequests(string projectId, UsageRequestsSchema usageRequestsSchema,
+        CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Get the details associated with the requestID
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="requestId">Id of request</param>
+    /// <returns><see cref="UsageRequestResponse"/></returns>
+    public Task<UsageRequestResponse> GetUsageRequest(string projectId, string requestId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Gets a summary of usage
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="getUsageSummarySchema">Usage summary options<see cref="UsageSummarySchema"/> </param>
+    /// <returns><see cref="UsageSummaryResponse"/></returns>
+    public Task<UsageSummaryResponse> GetUsageSummary(string projectId, UsageSummarySchema summarySchema,
+        CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Get usage fields 
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="fieldsSchema">Project usage request field options<see cref="UsageFieldsSchema"/></param>
+    /// <returns><see cref="UsageFieldsResponse"/></returns>
+    public Task<UsageFieldsResponse> GetUsageFields(string projectId, UsageFieldsSchema fieldsSchema,
+        CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+    #endregion
+
+    #region Balances
+    /// <summary>
+    /// Gets a list of balances
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <returns><see cref="BalancesResponse"/></returns>
+    public Task<BalancesResponse> GetBalances(string projectId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Get the balance details associated with the balance id
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="balanceId">Id of balance</param>
+    /// <returns><see cref="BalanceResponse"/></returns>
+    public Task<BalanceResponse> GetBalance(string projectId, string balanceId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+    #endregion
+}
diff --git a/Deepgram/Clients/Interfaces/v2/IOnPremClient.cs b/Deepgram/Clients/Interfaces/v2/IOnPremClient.cs
new file mode 100644
index 00000000..a62f2f27
--- /dev/null
+++ b/Deepgram/Clients/Interfaces/v2/IOnPremClient.cs
@@ -0,0 +1,20 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Clients.Interfaces.v2;
+
+/// <summary>
+// *********** WARNING ***********
+// This class provides the IOnPremClient implementation
+//
+// Deprecated: This class is deprecated. Use ISelfHostedClient instead.
+// This will be removed in a future release.
+//
+// This package is frozen and no new functionality will be added.
+// *********** WARNING ***********
+/// </summary>
+[Obsolete("Please use ISelfHostedClient instead", false)]
+public interface IOnPremClient : ISelfHostedClient
+{
+}
diff --git a/Deepgram/Clients/Interfaces/v2/IPreRecordedClient.cs b/Deepgram/Clients/Interfaces/v2/IPreRecordedClient.cs
new file mode 100644
index 00000000..7fcf8749
--- /dev/null
+++ b/Deepgram/Clients/Interfaces/v2/IPreRecordedClient.cs
@@ -0,0 +1,20 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Clients.Interfaces.v2;
+
+/// <summary>
+// *********** WARNING ***********
+// This is the IPreRecordedClient interface
+//
+// Deprecated: This class is deprecated. Use the `IListenRESTClient` function instead.
+// This will be removed in a future release.
+//
+// This class is frozen and no new functionality will be added.
+// *********** WARNING ***********
+/// </summary>
+[Obsolete("Please use IListenRESTClient instead", false)]
+public interface IPreRecordedClient : IListenRESTClient
+{
+}
diff --git a/Deepgram/Clients/Interfaces/v2/ISelfHostedClient.cs b/Deepgram/Clients/Interfaces/v2/ISelfHostedClient.cs
new file mode 100644
index 00000000..86a29064
--- /dev/null
+++ b/Deepgram/Clients/Interfaces/v2/ISelfHostedClient.cs
@@ -0,0 +1,48 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Models.SelfHosted.v1;
+
+namespace Deepgram.Clients.Interfaces.v2;
+
+/// <summary>
+/// Not currently being used
+/// </summary>
+public interface ISelfHostedClient
+{
+    /// <summary>
+    /// get a list of credentials associated with project
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <returns><see cref="CredentialsResponse"/></returns>
+    public Task<CredentialsResponse> ListCredentials(string projectId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Get credentials for the project that is associated with credential ID
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="credentialsId">Id of credentials</param>
+    /// <returns><see cref="CredentialResponse"/></returns>
+    public Task<CredentialResponse> GetCredentials(string projectId, string credentialsId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Remove credentials in the project associated with the credentials ID
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="credentialsId">Id of credentials</param>
+    /// <returns><see cref="MessageResponse"/></returns>
+    public Task<MessageResponse> DeleteCredentials(string projectId, string credentialsId, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    /// <summary>
+    /// Create credentials for the associated projects
+    /// </summary>
+    /// <param name="projectId">Id of project</param>
+    /// <param name="createOnPremCredentialsSchema"><see cref="CredentialsSchema"/> for credentials to be created</param>
+    /// <returns><see cref="CredentialResponse"/></returns>
+    public Task<CredentialResponse> CreateCredentials(string projectId, CredentialsSchema credentialsSchema,
+        CancellationTokenSource? cancellationToken = default, Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+}
diff --git a/Deepgram/Clients/Interfaces/v2/ISpeakClient.cs b/Deepgram/Clients/Interfaces/v2/ISpeakClient.cs
new file mode 100644
index 00000000..d5ad0807
--- /dev/null
+++ b/Deepgram/Clients/Interfaces/v2/ISpeakClient.cs
@@ -0,0 +1,20 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Clients.Interfaces.v2;
+
+/// <summary>
+/// *********** WARNING ***********
+/// This class provides the ISpeakClient implementation for the Deepgram API
+///
+/// Deprecated: This class is deprecated. Use the ISpeakRESTClient interface instead.
+/// This will be removed in a future release.
+///
+/// This package is frozen and no new functionality will be added.
+/// *********** WARNING ***********
+/// </summary>
+[Obsolete("Please use ISpeakRESTClient instead", false)]
+public interface ISpeakClient : ISpeakRESTClient
+{
+}
diff --git a/Deepgram/Clients/Interfaces/v2/ISpeakRESTClient.cs b/Deepgram/Clients/Interfaces/v2/ISpeakRESTClient.cs
new file mode 100644
index 00000000..8a1d4576
--- /dev/null
+++ b/Deepgram/Clients/Interfaces/v2/ISpeakRESTClient.cs
@@ -0,0 +1,39 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Models.Speak.v1.REST;
+
+namespace Deepgram.Clients.Interfaces.v2;
+
+/// <summary>
+/// Not currently being used
+/// </summary>
+public interface ISpeakRESTClient
+{
+    #region NoneCallBacks
+    /// <summary>
+    /// Speaks a file using the provided stream
+    /// </summary>
+    /// <param name="source">file is the form of a stream <see cref="Stream"/></param>
+    /// <param name="speakSchema">Options for the transcription <see cref="SpeakSchema"/></param>
+    /// <returns><see cref="SyncResponse"/></returns>
+    public Task<SyncResponse> ToStream(TextSource source, SpeakSchema? speakSchema, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+
+    public Task<SyncResponse> ToFile(TextSource source, string filename, SpeakSchema? speakSchema, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+    #endregion
+
+    #region  CallBack Methods
+    /// <summary>
+    /// Speaks a file using the provided byte array and providing a CallBack
+    /// </summary>
+    /// <param name="source">file is the form of a byte[]</param>  
+    /// <param name="callBack">CallBack url</param>    
+    /// <param name="speakSchema">Options for the transcription<see cref="SpeakSchema"></param>
+    /// <returns><see cref="AsyncResponse"/></returns>
+    public Task<AsyncResponse> StreamCallBack(TextSource source, string? callBack, SpeakSchema? speakSchema, CancellationTokenSource? cancellationToken = default,
+        Dictionary<string, string>? addons = null, Dictionary<string, string>? headers = null);
+    #endregion
+}
diff --git a/Deepgram/Clients/Interfaces/v2/ISpeakWebSocketClient.cs b/Deepgram/Clients/Interfaces/v2/ISpeakWebSocketClient.cs
new file mode 100644
index 00000000..48f01290
--- /dev/null
+++ b/Deepgram/Clients/Interfaces/v2/ISpeakWebSocketClient.cs
@@ -0,0 +1,159 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Models.Speak.v2.WebSocket;
+
+namespace Deepgram.Clients.Interfaces.v2;
+
+/// <summary>
+/// Implements version 2 of the Live Client.
+/// </summary>
+public interface ISpeakWebSocketClient
+{
+    #region Connect and Disconnect
+    public Task<bool> Connect(SpeakSchema options, CancellationTokenSource? cancelToken = null, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null);
+
+    public Task<bool> Stop(CancellationTokenSource? cancelToken = null, bool nullByte = false);
+    #endregion
+
+    #region Subscribe Event
+    /// <summary>
+    /// Subscribe to an Open event from the Deepgram API
+    /// </summary>
+    /// <param name="eventHandler"></param>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<OpenResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to a Metadata event from the Deepgram API
+    /// </summary>
+    /// <param name="eventHandler"></param>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<MetadataResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to a Flushed event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<FlushedResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to a Cleared event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<ClearedResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to a Audio buffer/event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<AudioResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to a Close event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<CloseResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to an Warning event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<WarningResponse> eventHandler);
+
+
+    /// <summary>
+    /// Subscribe to an Error event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<ErrorResponse> eventHandler);
+
+    /// <summary>
+    /// Subscribe to an Unhandled event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public Task<bool> Subscribe(EventHandler<UnhandledResponse> eventHandler);
+    #endregion
+
+    #region Send Functions
+    /// <summary>
+    /// Sends text data over the WebSocket connection.
+    /// </summary>
+    /// <param name="data"></param>
+    public void SpeakWithText(string data);
+
+    ///// <summary>
+    /////  This method Flushes the text buffer on Deepgram to be converted to audio
+    ///// </summary>
+    public void Flush();
+
+    ///// <summary>
+    /////  This method Resets the text buffer on Deepgram to be converted to audio
+    ///// </summary>
+    public void Clear();
+
+    ///// <summary>
+    /////  This method tells Deepgram to initiate the close server-side.
+    ///// </summary>
+    public void Close(bool nullByte = false);
+
+    ///// <summary>
+    ///// This method sends a binary message over the WebSocket connection.
+    ///// </summary>
+    ///// <param name="data"></param>
+    //public void SpeakWithStream(byte[] data);
+
+    /// <summary>
+    /// Sends a Close message to Deepgram
+    /// </summary>
+    public Task SendClose(bool nullByte = false);
+
+    /// <summary>
+    /// Sends a binary message over the WebSocket connection.
+    /// </summary>
+    /// <param name="data"></param>
+    /// <param name="length">The number of bytes from the data to send. Use `Constants.UseArrayLengthForSend` to send the entire array.</param>
+    public void Send(byte[] data, int length = Constants.UseArrayLengthForSend);
+
+    ///// <summary>
+    ///// This method sends a binary message over the WebSocket connection.
+    ///// </summary>
+    ///// <param name="data"></param>
+    //public void SendBinary(byte[] data, int length = Constants.UseArrayLengthForSend);
+
+    /// <summary>
+    /// This method sends a text message over the WebSocket connection.
+    /// </summary>
+    /// <param name="data"></param>
+    /// <param name="length">The number of bytes from the data to send. Use `Constants.UseArrayLengthForSend` to send the entire array.</param>
+    public void SendMessage(byte[] data, int length = Constants.UseArrayLengthForSend);
+
+    ///// <summary>
+    ///// This method sends a binary message over the WebSocket connection immediately without queueing.
+    ///// </summary>
+    //public Task SendBinaryImmediately(byte[] data, int length = Constants.UseArrayLengthForSend);
+
+    /// <summary>
+    /// This method sends a text message over the WebSocket connection immediately without queueing.
+    /// </summary>
+    /// <param name="data"></param>
+    /// <param name="length">The number of bytes from the data to send. Use `Constants.UseArrayLengthForSend` to send the entire array.</param>
+    public Task SendMessageImmediately(byte[] data, int length = Constants.UseArrayLengthForSend);
+    #endregion
+
+    #region Helpers
+    /// <summary>
+    /// Retrieves the connection state of the WebSocket
+    /// </summary>
+    /// <returns>Returns the connection state of the WebSocket</returns>
+    public WebSocketState State();
+
+    /// <summary>
+    /// Indicates whether the WebSocket is connected
+    /// </summary> 
+    /// <returns>Returns true if the WebSocket is connected</returns>
+    public bool IsConnected();
+    #endregion
+}
diff --git a/Deepgram/Clients/Interfaces/v2/ResponseEvent.cs b/Deepgram/Clients/Interfaces/v2/ResponseEvent.cs
new file mode 100644
index 00000000..e2440366
--- /dev/null
+++ b/Deepgram/Clients/Interfaces/v2/ResponseEvent.cs
@@ -0,0 +1,16 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Clients.Interfaces.v2;
+
+public class ResponseEvent<T>
+{
+    public T? Response { get; }
+
+    public ResponseEvent(T? response)
+    {
+        Response = response;
+    }
+}
+
diff --git a/Deepgram/Clients/Listen/v1/REST/Client.cs b/Deepgram/Clients/Listen/v1/REST/Client.cs
index 24c0effe..f4ae7f6d 100644
--- a/Deepgram/Clients/Listen/v1/REST/Client.cs
+++ b/Deepgram/Clients/Listen/v1/REST/Client.cs
@@ -5,6 +5,7 @@
 using Deepgram.Models.Authenticate.v1;
 using Deepgram.Models.Listen.v1.REST;
 using Deepgram.Clients.Interfaces.v1;
+using Deepgram.Abstractions.v1;
 
 namespace Deepgram.Clients.Listen.v1.REST;
 
diff --git a/Deepgram/Clients/Listen/v1/WebSocket/Client.cs b/Deepgram/Clients/Listen/v1/WebSocket/Client.cs
index b6c6c74b..c9529472 100644
--- a/Deepgram/Clients/Listen/v1/WebSocket/Client.cs
+++ b/Deepgram/Clients/Listen/v1/WebSocket/Client.cs
@@ -11,8 +11,16 @@
 namespace Deepgram.Clients.Listen.v1.WebSocket;
 
 /// <summary>
-/// Implements version 1 of the Live Client.
+// *********** WARNING ***********
+// Implements version 1 of the Listen WebSocket Client
+//
+// Deprecated: This class is deprecated. Use the `v2` of the client instead.
+// This will be removed in a future release.
+//
+// This class is frozen and no new functionality will be added.
+// *********** WARNING ***********
 /// </summary>
+[Obsolete("Please use Deepgram.Clients.Listen.v2.WebSocket instead", false)]
 public class Client : IDisposable, IListenWebSocketClient
 {
     #region Fields
diff --git a/Deepgram/Clients/Listen/v2/WebSocket/Client.cs b/Deepgram/Clients/Listen/v2/WebSocket/Client.cs
new file mode 100644
index 00000000..096cea4b
--- /dev/null
+++ b/Deepgram/Clients/Listen/v2/WebSocket/Client.cs
@@ -0,0 +1,658 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Abstractions.v2;
+using Deepgram.Models.Authenticate.v1;
+using Deepgram.Models.Listen.v2.WebSocket;
+using Common = Deepgram.Models.Common.v2.WebSocket;
+using Deepgram.Clients.Interfaces.v2;
+
+namespace Deepgram.Clients.Listen.v2.WebSocket;
+
+/// <summary>
+/// Implements version 2 of the Listen WebSocket Client.
+/// </summary>
+public class Client : AbstractWebSocketClient, IListenWebSocketClient
+{
+    #region Fields
+    private DateTime? _lastReceived = null;
+    private readonly SemaphoreSlim _mutexLastDatagram = new SemaphoreSlim(1, 1);
+    #endregion
+
+    /// <param name="apiKey">Required DeepgramApiKey</param>
+    /// <param name="deepgramClientOptions"><see cref="IDeepgramClientOptions"/> for HttpClient Configuration</param>
+    public Client(string? apiKey = null, IDeepgramClientOptions? options = null) : base(apiKey, options)
+    {
+        Log.Verbose("ListenWSClient", "ENTER");
+        Log.Debug("ListenWSClient", $"KeepAlive: {_deepgramClientOptions.KeepAlive}");
+        Log.Debug("ListenWSClient", $"Autoflush: {_deepgramClientOptions.AutoFlushReplyDelta}");
+        Log.Verbose("ListenWSClient", "LEAVE");
+    }
+
+    #region Event Handlers
+    /// <summary>
+    /// Fires when an event is received from the Deepgram API
+    /// </summary>
+    private event EventHandler<MetadataResponse>? _metadataReceived;
+    private event EventHandler<ResultResponse>? _resultsReceived;
+    private event EventHandler<UtteranceEndResponse>? _utteranceEndReceived;
+    private event EventHandler<SpeechStartedResponse>? _speechStartedReceived;
+    #endregion
+
+    /// <summary>
+    /// Connect to a Deepgram API Web Socket to begin transcribing audio
+    /// </summary>
+    /// <param name="options">Options to use when transcribing audio</param>
+    /// <returns>The task object representing the asynchronous operation.</returns>
+    public async Task<bool> Connect(LiveSchema options, CancellationTokenSource? cancelToken = null, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("ListenWSClient.Connect", "ENTER");
+        Log.Information("Connect", $"options:\n{JsonSerializer.Serialize(options, JsonSerializeOptions.DefaultOptions)}");
+        Log.Debug("Connect", $"addons: {addons}");
+
+        try
+        {
+            var myURI = GetUri(_deepgramClientOptions, options, addons);
+            Log.Debug("Connect", $"uri: {myURI}");
+            bool bConnected = await base.Connect(myURI.ToString(), cancelToken, headers);
+            if (!bConnected)
+            {
+                Log.Warning("Connect", "Connect failed");
+                Log.Verbose("ListenWSClient.Connect", "LEAVE");
+                return false;
+            }
+
+            if (_deepgramClientOptions.KeepAlive)
+            {
+                Log.Debug("Connect", "Starting KeepAlive Thread...");
+                StartKeepAliveBackgroundThread();
+            }
+
+            if (_deepgramClientOptions.AutoFlushReplyDelta > 0)
+            {
+                Log.Debug("Connect", "Starting AutoFlush Thread...");
+                StartAutoFlushBackgroundThread();
+            }
+
+            Log.Debug("Connect", "Connect Succeeded");
+            Log.Verbose("ListenWSClient.Connect", "LEAVE");
+
+            return true;
+        }
+        catch (TaskCanceledException ex)
+        {
+            Log.Debug("Connect", "Connect cancelled.");
+            Log.Verbose("Connect", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("ListenWSClient.Connect", "LEAVE");
+
+            return false;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("Connect", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("Connect", $"Exception: {ex}");
+            Log.Verbose("ListenWSClient.Connect", "LEAVE");
+            throw;
+        }
+
+        void StartKeepAliveBackgroundThread() => Task.Run(async () => await ProcessKeepAlive());
+
+        void StartAutoFlushBackgroundThread() => Task.Run(async () => await ProcessAutoFlush());
+    }
+
+    #region Subscribe Event
+    /// <summary>
+    /// Subscribe to an Open event from the Deepgram API
+    /// </summary>
+    /// <param name="eventHandler"></param>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<OpenResponse> eventHandler)
+    {
+        // Create a new event handler that wraps the original one
+        EventHandler<Common.OpenResponse> wrappedHandler = (sender, args) =>
+        {
+            // Cast the event arguments to the desired type
+            var castedArgs = new OpenResponse();
+            castedArgs.Copy(args);
+            if (castedArgs != null)
+            {
+                // Invoke the original event handler with the casted arguments
+                eventHandler(sender, castedArgs);
+            }
+        };
+
+        // Pass the new event handler to the base Subscribe method
+        return await base.Subscribe(wrappedHandler);
+    }
+
+    /// <summary>
+    /// Subscribe to a Metadata event from the Deepgram API
+    /// </summary>
+    /// <param name="eventHandler"></param>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<MetadataResponse> eventHandler)
+    {
+        await _mutexSubscribe.WaitAsync();
+        try
+        {
+            _metadataReceived += (sender, e) => eventHandler(sender, e);
+        }
+        finally
+        {
+            _mutexSubscribe.Release();
+        }
+        return true;
+    }
+
+    /// <summary>
+    /// Subscribe to a Results event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<ResultResponse> eventHandler)
+    {
+        await _mutexSubscribe.WaitAsync();
+        try
+        {
+            _resultsReceived += (sender, e) => eventHandler(sender, e);
+        }
+        finally
+        {
+            _mutexSubscribe.Release();
+        }
+        return true;
+    }
+
+    /// <summary>
+    /// Subscribe to an UtteranceEnd event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<UtteranceEndResponse> eventHandler)
+    {
+        await _mutexSubscribe.WaitAsync();
+        try
+        {
+            _utteranceEndReceived += (sender, e) => eventHandler(sender, e);
+        }
+        finally
+        {
+            _mutexSubscribe.Release();
+        }
+        return true;
+    }
+
+    /// <summary>
+    /// Subscribe to a SpeechStarted event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<SpeechStartedResponse> eventHandler)
+    {
+        await _mutexSubscribe.WaitAsync();
+        try
+        {
+            _speechStartedReceived += (sender, e) => eventHandler(sender, e);
+        }
+        finally
+        {
+            _mutexSubscribe.Release();
+        }
+        return true;
+    }
+
+    /// <summary>
+    /// Subscribe to an Close event from the Deepgram API
+    /// </summary>
+    /// <param name="eventHandler"></param>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<CloseResponse> eventHandler)
+    {
+        // Create a new event handler that wraps the original one
+        EventHandler<Common.CloseResponse> wrappedHandler = (sender, args) =>
+        {
+            // Cast the event arguments to the desired type
+            var castedArgs = new CloseResponse();
+            castedArgs.Copy(args);
+            if (castedArgs != null)
+            {
+                // Invoke the original event handler with the casted arguments
+                eventHandler(sender, castedArgs);
+            }
+        };
+
+        return await base.Subscribe(wrappedHandler);
+    }
+
+    /// <summary>
+    /// Subscribe to an Error event from the Deepgram API
+    /// </summary>
+    /// <param name="eventHandler"></param>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<ErrorResponse> eventHandler)
+    {
+        // Create a new event handler that wraps the original one
+        EventHandler<Common.ErrorResponse> wrappedHandler = (sender, args) =>
+        {
+            // Cast the event arguments to the desired type
+            var castedArgs = new ErrorResponse();
+            castedArgs.Copy(args);
+            if (castedArgs != null)
+            {
+                // Invoke the original event handler with the casted arguments
+                eventHandler(sender, castedArgs);
+            }
+        };
+
+        return await base.Subscribe(wrappedHandler);
+    }
+
+    /// <summary>
+    /// Subscribe to an Unhandled event from the Deepgram API
+    /// </summary>
+    /// <param name="eventHandler"></param>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<UnhandledResponse> eventHandler)
+    {
+        // Create a new event handler that wraps the original one
+        EventHandler<Common.UnhandledResponse> wrappedHandler = (sender, args) =>
+        {
+            // Cast the event arguments to the desired type
+            var castedArgs = new UnhandledResponse();
+            castedArgs.Copy(args);
+            if (castedArgs != null)
+            {
+                // Invoke the original event handler with the casted arguments
+                eventHandler(sender, castedArgs);
+            }
+        };
+
+        return await base.Subscribe(wrappedHandler);
+    }
+    #endregion
+
+    #region Send Functions
+    /// <summary>
+    /// Sends a KeepAlive message to Deepgram
+    /// </summary>
+    public async Task SendKeepAlive()
+    {
+        Log.Debug("SendKeepAlive", "Sending KeepAlive Message Immediately...");
+        byte[] data = Encoding.ASCII.GetBytes("{\"type\": \"KeepAlive\"}");
+        await SendMessageImmediately(data);
+    }
+
+    /// <summary>
+    /// Sends a Finalize message to Deepgram
+    /// </summary>
+    public async Task SendFinalize()
+    {
+        Log.Debug("SendFinalize", "Sending Finalize Message Immediately...");
+        byte[] data = Encoding.ASCII.GetBytes("{\"type\": \"Finalize\"}");
+        await SendMessageImmediately(data);
+    }
+
+    /// <summary>
+    /// Sends a Close message to Deepgram
+    /// </summary>
+    public override async Task SendClose(bool nullByte = false)
+    {
+        if (_clientWebSocket == null || !IsConnected())
+        {
+            Log.Warning("SendClose", "ClientWebSocket is null or not connected. Skipping...");
+            return;
+        }
+
+        Log.Debug("SendClose", "Sending Close Message Immediately...");
+        if (nullByte)
+        {
+            // send a close to Deepgram
+            await _mutexSend.WaitAsync(_cancellationTokenSource.Token);
+            try
+            {
+                await _clientWebSocket.SendAsync(new ArraySegment<byte>(new byte[1] { 0 }), WebSocketMessageType.Binary, true, _cancellationTokenSource.Token)
+                    .ConfigureAwait(false);
+            }
+            finally
+            {
+                _mutexSend.Release();
+            }
+            return;
+        }
+
+        byte[] data = Encoding.ASCII.GetBytes("{\"type\": \"CloseStream\"}");
+        await SendMessageImmediately(data);
+    }
+    #endregion
+
+    internal async Task ProcessKeepAlive()
+    {
+        Log.Verbose("ListenWSClient.ProcessKeepAlive", "ENTER");
+
+        try
+        {
+            while (true)
+            {
+                Log.Verbose("ProcessKeepAlive", "Waiting for KeepAlive...");
+                await Task.Delay(5000, _cancellationTokenSource.Token);
+
+                if (_cancellationTokenSource.Token.IsCancellationRequested)
+                {
+                    Log.Information("ProcessKeepAlive", "KeepAliveThread cancelled");
+                    break;
+                }
+                if (!IsConnected())
+                {
+                    Log.Debug("ProcessAutoFlush", "WebSocket is not connected. Exiting...");
+                    break;
+                }
+
+                await SendKeepAlive();
+            }
+
+            Log.Verbose("ProcessKeepAlive", "Exit");
+            Log.Verbose("ListenWSClient.ProcessKeepAlive", "LEAVE");
+        }
+        catch (TaskCanceledException ex)
+        {
+            Log.Debug("ProcessKeepAlive", "KeepAliveThread cancelled.");
+            Log.Verbose("ProcessKeepAlive", $"KeepAliveThread cancelled. Info: {ex}");
+            Log.Verbose("ListenWSClient.ProcessKeepAlive", "LEAVE");
+        }
+        catch (Exception ex)
+        {
+            Log.Error("ProcessKeepAlive", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("ProcessKeepAlive", $"Exception: {ex}");
+            Log.Verbose("ListenWSClient.ProcessKeepAlive", "LEAVE");
+        }
+    }
+
+    internal async Task ProcessAutoFlush()
+    {
+        Log.Verbose("ListenWSClient.ProcessAutoFlush", "ENTER");
+
+        var diffTicks = TimeSpan.FromMilliseconds((double)_deepgramClientOptions.AutoFlushReplyDelta);
+
+        try
+        {
+            while (true)
+            {
+                Log.Verbose("ProcessAutoFlush", "Waiting for AutoFlush...");
+                await Task.Delay(Constants.DefaultFlushPeriodInMs, _cancellationTokenSource.Token);
+
+                if (_cancellationTokenSource.Token.IsCancellationRequested)
+                {
+                    Log.Information("ProcessAutoFlush", "ProcessAutoFlush cancelled");
+                    break;
+                }
+                if (!IsConnected())
+                {
+                    Log.Debug("ProcessAutoFlush", "WebSocket is not connected. Exiting...");
+                    break;
+                }
+
+                await _mutexLastDatagram.WaitAsync();
+                try
+                {
+                    if (_lastReceived == null)
+                    {
+                        Log.Debug("ProcessAutoFlush", "No datagram received. Skipping...");
+                        continue;
+                    }
+
+                    var deltaTicks = DateTime.Now - _lastReceived;
+                    if (deltaTicks < diffTicks)
+                    {
+                        Log.Debug("ProcessAutoFlush", $"AutoFlush delta is less than threshold: {deltaTicks}. Skipping...");
+                        continue;
+                    }
+
+                    await SendFinalize();
+                    _lastReceived = null;
+                }
+                finally
+                {
+                    _mutexLastDatagram.Release();
+                }
+            }
+
+            Log.Verbose("ProcessAutoFlush", "Exit");
+            Log.Verbose("ListenWSClient.ProcessAutoFlush", "LEAVE");
+        }
+        catch (TaskCanceledException ex)
+        {
+            Log.Debug("ProcessAutoFlush", "ProcessAutoFlush cancelled.");
+            Log.Verbose("ProcessAutoFlush", $"ProcessAutoFlush cancelled. Info: {ex}");
+            Log.Verbose("ListenWSClient.ProcessAutoFlush", "LEAVE");
+        }
+        catch (Exception ex)
+        {
+            Log.Error("ProcessAutoFlush", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("ProcessAutoFlush", $"Exception: {ex}");
+            Log.Verbose("ListenWSClient.ProcessAutoFlush", "LEAVE");
+        }
+    }
+
+    internal override void ProcessTextMessage(WebSocketReceiveResult result, MemoryStream ms)
+    {
+        Log.Verbose("ListenWSClient.ProcessTextMessage", "ENTER");
+
+        ms.Seek(0, SeekOrigin.Begin);
+
+        var response = Encoding.UTF8.GetString(ms.ToArray());
+        if (response == null)
+        {
+            Log.Warning("ProcessTextMessage", "Response is null");
+            Log.Verbose("ListenWSClient.ProcessTextMessage", "LEAVE");
+            return;
+        }
+
+        try
+        {
+            Log.Verbose("ProcessTextMessage", $"raw response: {response}");
+            var data = JsonDocument.Parse(response);
+            var val = Enum.Parse(typeof(ListenType), data.RootElement.GetProperty("type").GetString()!);
+
+            Log.Verbose("ProcessTextMessage", $"Type: {val}");
+
+
+            if (_deepgramClientOptions.InspectListenMessage())
+            {
+                Log.Debug("ProcessTextMessage", "Call InspectMessage...");
+                InspectMessage(val, data).Wait();
+            }
+
+            switch (val)
+            {
+                case ListenType.Open:
+                case ListenType.Close:
+                case ListenType.Error:
+                    Log.Debug("ProcessTextMessage", "Calling base.ProcessTextMessage...");
+                    base.ProcessTextMessage(result, ms);
+                    break;
+                case ListenType.Results:
+                    var resultResponse = data.Deserialize<ResultResponse>();
+                    if (_resultsReceived == null)
+                    {
+                        Log.Debug("ProcessTextMessage", "_resultsReceived has no listeners");
+                        Log.Verbose("ProcessTextMessage", "LEAVE");
+                        return;
+                    }
+                    if (resultResponse == null)
+                    {
+                        Log.Warning("ProcessTextMessage", "ResultResponse is invalid");
+                        Log.Verbose("ProcessTextMessage", "LEAVE");
+                        return;
+                    }
+
+                    Log.Debug("ProcessTextMessage", $"Invoking ResultsResponse. event: {resultResponse}");
+                    InvokeParallel(_resultsReceived, resultResponse);
+                    break;
+                case ListenType.Metadata:
+                    var metadataResponse = data.Deserialize<MetadataResponse>();
+                    if (_metadataReceived == null)
+                    {
+                        Log.Debug("ProcessTextMessage", "_metadataReceived has no listeners");
+                        Log.Verbose("ProcessTextMessage", "LEAVE");
+                        return;
+                    }
+                    if (metadataResponse == null)
+                    {
+                        Log.Warning("ProcessTextMessage", "MetadataResponse is invalid");
+                        Log.Verbose("ProcessTextMessage", "LEAVE");
+                        return;
+                    }
+
+                    Log.Debug("ProcessTextMessage", $"Invoking MetadataResponse. event: {metadataResponse}");
+                    InvokeParallel(_metadataReceived, metadataResponse);
+                    break;
+                case ListenType.UtteranceEnd:
+                    var utteranceEndResponse = data.Deserialize<UtteranceEndResponse>();
+                    if (_utteranceEndReceived == null)
+                    {
+                        Log.Debug("ProcessTextMessage", "_utteranceEndReceived has no listeners");
+                        Log.Verbose("ProcessTextMessage", "LEAVE");
+                        return;
+                    }
+                    if (utteranceEndResponse == null)
+                    {
+                        Log.Warning("ProcessTextMessage", "UtteranceEndResponse is invalid");
+                        Log.Verbose("ProcessTextMessage", "LEAVE");
+                        return;
+                    }
+
+                    Log.Debug("ProcessTextMessage", $"Invoking UtteranceEndResponse. event: {utteranceEndResponse}");
+                    InvokeParallel(_utteranceEndReceived, utteranceEndResponse);
+                    break;
+                case ListenType.SpeechStarted:
+                    var speechStartedResponse = data.Deserialize<SpeechStartedResponse>();
+                    if (_speechStartedReceived == null)
+                    {
+                        Log.Debug("ProcessTextMessage", "_speechStartedReceived has no listeners");
+                        Log.Verbose("ProcessTextMessage", "LEAVE");
+                        return;
+                    }
+                    if (speechStartedResponse == null)
+                    {
+                        Log.Warning("ProcessTextMessage", "SpeechStartedResponse is invalid");
+                        Log.Verbose("ProcessTextMessage", "LEAVE");
+                        return;
+                    }
+
+                    Log.Debug("ProcessTextMessage", $"Invoking SpeechStartedResponse. event: {speechStartedResponse}");
+                    InvokeParallel(_speechStartedReceived, speechStartedResponse);
+                    break;
+                default:
+                    Log.Debug("ProcessTextMessage", "Calling base.ProcessTextMessage...");
+                    base.ProcessTextMessage(result, ms);
+                    break;
+            }
+
+            Log.Debug("ProcessTextMessage", "Succeeded");
+            Log.Verbose("ListenWSClient.ProcessTextMessage", "LEAVE");
+        }
+        catch (JsonException ex)
+        {
+            Log.Error("ProcessTextMessage", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("ProcessTextMessage", $"Exception: {ex}");
+            Log.Verbose("ListenWSClient.ProcessTextMessage", "LEAVE");
+        }
+        catch (Exception ex)
+        {
+            Log.Error("ProcessTextMessage", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("ProcessTextMessage", $"Exception: {ex}");
+            Log.Verbose("ListenWSClient.ProcessTextMessage", "LEAVE");
+        }
+    }
+
+    #region Helpers
+    /// <summary>
+    /// Get the URI for the WebSocket connection
+    /// </summary> 
+    internal static Uri GetUri(IDeepgramClientOptions options, LiveSchema parameter, Dictionary<string, string>? addons = null)
+    {
+        var propertyInfoList = parameter.GetType()
+            .GetProperties()
+            .Where(v => v.GetValue(parameter) is not null);
+
+        var queryString = QueryParameterUtil.UrlEncode(parameter, propertyInfoList, addons);
+
+        return new Uri($"{options.BaseAddress}/{UriSegments.LISTEN}?{queryString}");
+    }
+
+    private async Task InspectMessage(object type, JsonDocument data)
+    {
+        Log.Verbose("InspectMessage", "ENTER");
+
+        try
+        {
+            switch (type)
+            {
+                case ListenType.Results:
+                    var resultResponse = data.Deserialize<ResultResponse>();
+                    if (resultResponse == null)
+                    {
+                        Log.Warning("InspectMessage", "ResultResponse is invalid");
+                        Log.Verbose("InspectMessage", "LEAVE");
+                        return;
+                    }
+
+                    var sentence = resultResponse.Channel.Alternatives[0].Transcript;
+
+                    if (resultResponse.Channel.Alternatives.Count == 0 || sentence == "") {
+                        Log.Verbose("InspectMessage", $"resultResponse has empty message");
+                        Log.Verbose("InspectMessage", "LEAVE");
+                        return;
+                    }
+
+                    if (_deepgramClientOptions.AutoFlushReplyDelta > 0)
+                    {
+                        if ((bool)resultResponse.IsFinal)
+                        {
+                            var now = DateTime.Now;
+                            Log.Debug("InspectMessage", $"AutoFlush IsFinal received. Time: {now}");
+                            await _mutexLastDatagram.WaitAsync();
+                            try
+                            {
+                                _lastReceived = null;
+                            }
+                            finally
+                            {
+                                _mutexLastDatagram.Release();
+                            }
+                        }
+                        else
+                        {
+                            var now = DateTime.Now;
+                            Log.Debug("InspectMessage", $"AutoFlush Interim received. Time: {now}");
+                            await _mutexLastDatagram.WaitAsync();
+                            try
+                            {
+                                _lastReceived = now;
+                            }
+                            finally
+                            {
+                                _mutexLastDatagram.Release();
+                            }
+                        }
+                    }
+                    break;
+            }
+
+            Log.Debug("InspectMessage", "Succeeded");
+            Log.Verbose("InspectMessage", "LEAVE");
+        }
+        catch (JsonException ex)
+        {
+            Log.Error("InspectMessage", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("InspectMessage", $"Exception: {ex}");
+            Log.Verbose("ListenWSClient.InspectMessage", "LEAVE");
+        }
+        catch (Exception ex)
+        {
+            Log.Error("InspectMessage", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("InspectMessage", $"Exception: {ex}");
+            Log.Verbose("ListenWSClient.InspectMessage", "LEAVE");
+        }
+    }
+    #endregion
+}
diff --git a/Deepgram/Clients/Listen/v2/WebSocket/Constants.cs b/Deepgram/Clients/Listen/v2/WebSocket/Constants.cs
new file mode 100644
index 00000000..7bddab15
--- /dev/null
+++ b/Deepgram/Clients/Listen/v2/WebSocket/Constants.cs
@@ -0,0 +1,15 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Clients.Listen.v2.WebSocket;
+
+/// <summary> 
+/// Headers of interest in the return values from the Deepgram Speak API.
+/// </summary> 
+public static class Constants
+{
+    // Default flush period
+    public const int DefaultFlushPeriodInMs = 500;
+}
+
diff --git a/Deepgram/Clients/Listen/v2/WebSocket/ResponseEvent.cs b/Deepgram/Clients/Listen/v2/WebSocket/ResponseEvent.cs
new file mode 100644
index 00000000..2cd1a118
--- /dev/null
+++ b/Deepgram/Clients/Listen/v2/WebSocket/ResponseEvent.cs
@@ -0,0 +1,11 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Clients.Listen.v2.WebSocket;
+
+public class ResponseEvent<T>(T? response) : EventArgs
+{
+    public T? Response { get; set; } = response;
+}
+
diff --git a/Deepgram/Clients/Listen/v2/WebSocket/UriSegments.cs b/Deepgram/Clients/Listen/v2/WebSocket/UriSegments.cs
new file mode 100644
index 00000000..a51f2f8a
--- /dev/null
+++ b/Deepgram/Clients/Listen/v2/WebSocket/UriSegments.cs
@@ -0,0 +1,12 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Clients.Listen.v2.WebSocket;
+
+public static class UriSegments
+{
+    //using constants instead of inline value(magic strings) make consistence
+    //across SDK And Test Projects Simpler and Easier to change
+    public const string LISTEN = "listen";
+}
diff --git a/Deepgram/Clients/Manage/v1/Client.cs b/Deepgram/Clients/Manage/v1/Client.cs
index 990530f2..2f6e72b2 100644
--- a/Deepgram/Clients/Manage/v1/Client.cs
+++ b/Deepgram/Clients/Manage/v1/Client.cs
@@ -5,6 +5,7 @@
 using Deepgram.Models.Authenticate.v1;
 using Deepgram.Models.Manage.v1;
 using Deepgram.Clients.Interfaces.v1;
+using Deepgram.Abstractions.v1;
 
 namespace Deepgram.Clients.Manage.v1;
 
diff --git a/Deepgram/Clients/SelfHosted/v1/Client.cs b/Deepgram/Clients/SelfHosted/v1/Client.cs
index d3864562..5560e3d6 100644
--- a/Deepgram/Clients/SelfHosted/v1/Client.cs
+++ b/Deepgram/Clients/SelfHosted/v1/Client.cs
@@ -5,6 +5,7 @@
 using Deepgram.Models.Authenticate.v1;
 using Deepgram.Models.SelfHosted.v1;
 using Deepgram.Clients.Interfaces.v1;
+using Deepgram.Abstractions.v1;
 
 namespace Deepgram.Clients.SelfHosted.v1;
 
diff --git a/Deepgram/Clients/Speak/v1/REST/Client.cs b/Deepgram/Clients/Speak/v1/REST/Client.cs
index 114d07be..c554fc6d 100644
--- a/Deepgram/Clients/Speak/v1/REST/Client.cs
+++ b/Deepgram/Clients/Speak/v1/REST/Client.cs
@@ -5,6 +5,7 @@
 using Deepgram.Models.Speak.v1.REST;
 using Deepgram.Models.Authenticate.v1;
 using Deepgram.Clients.Interfaces.v1;
+using Deepgram.Abstractions.v1;
 
 namespace Deepgram.Clients.Speak.v1.REST;
 
diff --git a/Deepgram/Clients/Speak/v1/WebSocket/Client.cs b/Deepgram/Clients/Speak/v1/WebSocket/Client.cs
index 7403693a..97bc2979 100644
--- a/Deepgram/Clients/Speak/v1/WebSocket/Client.cs
+++ b/Deepgram/Clients/Speak/v1/WebSocket/Client.cs
@@ -11,8 +11,16 @@
 namespace Deepgram.Clients.Speak.v1.WebSocket;
 
 /// <summary>
-/// Implements version 1 of the Live Client.
+// *********** WARNING ***********
+// Implements version 1 of the Speak WebSocket Client
+//
+// Deprecated: This class is deprecated. Use the `v2` of the client instead.
+// This will be removed in a future release.
+//
+// This class is frozen and no new functionality will be added.
+// *********** WARNING ***********
 /// </summary>
+[Obsolete("Please use Deepgram.Clients.Speak.v2.WebSocket instead", false)]
 public class Client : IDisposable, ISpeakWebSocketClient
 {
     #region Fields
diff --git a/Deepgram/Clients/Speak/v1/WebSocket/ResponseEvent.cs b/Deepgram/Clients/Speak/v1/WebSocket/ResponseEvent.cs
index 01dbcc7a..4741f887 100644
--- a/Deepgram/Clients/Speak/v1/WebSocket/ResponseEvent.cs
+++ b/Deepgram/Clients/Speak/v1/WebSocket/ResponseEvent.cs
@@ -4,8 +4,13 @@
 
 namespace Deepgram.Clients.Speak.v1.WebSocket;
 
-public class ResponseEvent<T>(T? response) : EventArgs
+public class ResponseEvent<T> : EventArgs
 {
-    public T? Response { get; set; } = response;
+    public T? Response { get; }
+
+    public ResponseEvent(T? response)
+    {
+        Response = response;
+    }
 }
 
diff --git a/Deepgram/Clients/Speak/v2/WebSocket/Client.cs b/Deepgram/Clients/Speak/v2/WebSocket/Client.cs
new file mode 100644
index 00000000..0677917a
--- /dev/null
+++ b/Deepgram/Clients/Speak/v2/WebSocket/Client.cs
@@ -0,0 +1,711 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Abstractions.v2;
+using Deepgram.Models.Authenticate.v1;
+using Deepgram.Models.Speak.v2.WebSocket;
+using Common = Deepgram.Models.Common.v2.WebSocket;
+using Deepgram.Clients.Interfaces.v2;
+
+namespace Deepgram.Clients.Speak.v2.WebSocket;
+
+/// <summary>
+/// Implements version 2 of the Speak WebSocket Client.
+/// </summary>
+public class Client : AbstractWebSocketClient, ISpeakWebSocketClient
+{
+    #region Fields
+    private DateTime? _lastReceived = null;
+    private int _flushCount = 0;
+    private readonly SemaphoreSlim _mutexLastDatagram = new SemaphoreSlim(1, 1);
+    #endregion
+
+    /// <param name="apiKey">Required DeepgramApiKey</param>
+    /// <param name="deepgramClientOptions"><see cref="IDeepgramClientOptions"/> for HttpClient Configuration</param>
+    public Client(string? apiKey = null, IDeepgramClientOptions? options = null) : base(apiKey, options)
+    {
+        Log.Verbose("SpeakWSClient", "ENTER");
+        Log.Debug("SpeakWSClient", $"Autoflush: {_deepgramClientOptions.AutoFlushSpeakDelta}");
+        Log.Verbose("SpeakWSClient", "LEAVE");
+    }
+
+    #region Event Handlers
+    /// <summary>
+    /// Fires when an event is received from the Deepgram API
+    /// </summary>
+    private event EventHandler<MetadataResponse>? _metadataReceived;
+    private event EventHandler<FlushedResponse>? _flushedReceived;
+    private event EventHandler<ClearedResponse>? _clearedReceived;
+    private event EventHandler<AudioResponse>? _audioReceived;
+    private event EventHandler<WarningResponse>? _warningReceived;
+    #endregion
+
+    /// <summary>
+    /// Connect to a Deepgram API Web Socket to begin transcribing audio
+    /// </summary>
+    /// <param name="options">Options to use when transcribing audio</param>
+    /// <returns>The task object representing the asynchronous operation.</returns>
+    public async Task<bool> Connect(SpeakSchema options, CancellationTokenSource? cancelToken = null, Dictionary<string, string>? addons = null,
+        Dictionary<string, string>? headers = null)
+    {
+        Log.Verbose("SpeakWSClient.Connect", "ENTER");
+        Log.Information("Connect", $"options:\n{JsonSerializer.Serialize(options, JsonSerializeOptions.DefaultOptions)}");
+        Log.Debug("Connect", $"addons: {addons}");
+
+        try
+        {
+            var myURI = GetUri(_deepgramClientOptions, options, addons);
+            Log.Debug("Connect", $"uri: {myURI}");
+            bool bConnected = await base.Connect(myURI.ToString(), cancelToken, headers);
+            if (!bConnected)
+            {
+                Log.Warning("Connect", "Connect failed");
+                Log.Verbose("SpeakWSClient.Connect", "LEAVE");
+                return false;
+            }
+
+            if (_deepgramClientOptions.AutoFlushSpeakDelta > 0)
+            {
+                Log.Debug("Connect", "Starting AutoFlush Thread...");
+                StartAutoFlushBackgroundThread();
+            }
+
+            Log.Debug("Connect", "Connect Succeeded");
+            Log.Verbose("SpeakWSClient.Connect", "LEAVE");
+
+            return true;
+        }
+        catch (TaskCanceledException ex)
+        {
+            Log.Debug("Connect", "Connect cancelled.");
+            Log.Verbose("Connect", $"Connect cancelled. Info: {ex}");
+            Log.Verbose("SpeakWSClient.Connect", "LEAVE");
+
+            return false;
+        }
+        catch (Exception ex)
+        {
+            Log.Error("Connect", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("Connect", $"Excepton: {ex}");
+            Log.Verbose("SpeakWSClient.Connect", "LEAVE");
+            throw;
+        }
+
+        void StartAutoFlushBackgroundThread() => Task.Run(async () => await ProcessAutoFlush());
+    }
+
+    #region Subscribe Event
+    /// <summary>
+    /// Subscribe to an Open event from the Deepgram API
+    /// </summary>
+    /// <param name="eventHandler"></param>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<OpenResponse> eventHandler)
+    {
+        // Create a new event handler that wraps the original one
+        EventHandler<Common.OpenResponse> wrappedHandler = (sender, args) =>
+        {
+            // Cast the event arguments to the desired type
+            var castedArgs = new OpenResponse();
+            castedArgs.Copy(args);
+            if (castedArgs != null)
+            {
+                // Invoke the original event handler with the casted arguments
+                eventHandler(sender, castedArgs);
+            }
+        };
+
+        // Pass the new event handler to the base Subscribe method
+        return await base.Subscribe(wrappedHandler);
+    }
+
+    /// <summary>
+    /// Subscribe to a Metadata event from the Deepgram API
+    /// </summary>
+    /// <param name="eventHandler"></param>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<MetadataResponse> eventHandler)
+    {
+        await _mutexSubscribe.WaitAsync();
+        try
+        {
+            _metadataReceived += (sender, e) => eventHandler(sender, e);
+        }
+        finally
+        {
+            _mutexSubscribe.Release();
+        }
+        return true;
+    }
+
+    /// <summary>
+    /// Subscribe to a Flushed event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<FlushedResponse> eventHandler)
+    {
+        await _mutexSubscribe.WaitAsync();
+        try
+        {
+            _flushedReceived += (sender, e) => eventHandler(sender, e);
+        }
+        finally
+        {
+            _mutexSubscribe.Release();
+        }
+        return true;
+    }
+
+    /// <summary>
+    /// Subscribe to a Cleared event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<ClearedResponse> eventHandler)
+    {
+        await _mutexSubscribe.WaitAsync();
+        try
+        {
+            _clearedReceived += (sender, e) => eventHandler(sender, e);
+        }
+        finally
+        {
+            _mutexSubscribe.Release();
+        }
+        return true;
+    }
+
+    /// <summary>
+    /// Subscribe to an Audio event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<AudioResponse> eventHandler)
+    {
+        await _mutexSubscribe.WaitAsync();
+        try
+        {
+            _audioReceived += (sender, e) => eventHandler(sender, e);
+        }
+        finally
+        {
+            _mutexSubscribe.Release();
+        }
+        return true;
+    }
+
+    /// <summary>
+    /// Subscribe to a Close event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<CloseResponse> eventHandler)
+    {
+        // Create a new event handler that wraps the original one
+        EventHandler<Common.CloseResponse> wrappedHandler = (sender, args) =>
+        {
+            // Cast the event arguments to the desired type
+            var castedArgs = new CloseResponse();
+            castedArgs.Copy(args);
+            if (castedArgs != null)
+            {
+                // Invoke the original event handler with the casted arguments
+                eventHandler(sender, castedArgs);
+            }
+        };
+
+        return await base.Subscribe(wrappedHandler);
+    }
+
+    /// <summary>
+    /// Subscribe to an Warning event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<WarningResponse> eventHandler)
+    {
+        await _mutexSubscribe.WaitAsync();
+        try
+        {
+            _warningReceived += (sender, e) => eventHandler(sender, e);
+        }
+        finally
+        {
+            _mutexSubscribe.Release();
+        }
+        return true;
+    }
+
+    /// <summary>
+    /// Subscribe to an Error event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<ErrorResponse> eventHandler)
+    {
+        // Create a new event handler that wraps the original one
+        EventHandler<Common.ErrorResponse> wrappedHandler = (sender, args) =>
+        {
+            // Cast the event arguments to the desired type
+            var castedArgs = new ErrorResponse();
+            castedArgs.Copy(args);
+            if (castedArgs != null)
+            {
+                // Invoke the original event handler with the casted arguments
+                eventHandler(sender, castedArgs);
+            }
+        };
+
+        return await base.Subscribe(wrappedHandler);
+    }
+
+    /// <summary>
+    /// Subscribe to an Unhandled event from the Deepgram API
+    /// </summary>
+    /// <returns>True if successful</returns>
+    public async Task<bool> Subscribe(EventHandler<UnhandledResponse> eventHandler)
+    {
+        // Create a new event handler that wraps the original one
+        EventHandler<Common.UnhandledResponse> wrappedHandler = (sender, args) =>
+        {
+            // Cast the event arguments to the desired type
+            var castedArgs = new UnhandledResponse();
+            castedArgs.Copy(args);
+            if (castedArgs != null)
+            {
+                // Invoke the original event handler with the casted arguments
+                eventHandler(sender, castedArgs);
+            }
+        };
+
+        return await base.Subscribe(wrappedHandler);
+    }
+    #endregion
+
+    #region Send Functions
+    /// <summary>
+    /// This method sends a string to Deepgram for conversion to audio.
+    /// This is a convenience functions that will wrap the provided string in a TextSource object.
+    /// NOTE: These should never use the SendImmediately methods because they would by-pass the flow of text messages queued.
+    /// </summary>
+    /// <param name="text">The string of text you want to be converted to audio.</param>
+    public void SpeakWithText(string text)
+    {
+        TextSource textSource = new TextSource(text);
+        byte[] byteArray = Encoding.UTF8.GetBytes(textSource.ToString());
+        SendMessage(byteArray);
+    }
+
+    /// <summary>
+    ///  This method Flushes the text buffer on Deepgram to be converted to audio.
+    ///  NOTE: These should never use the SendImmediately methods because they would by-pass the flow of text messages queued.
+    /// </summary>
+    public void Flush()
+    {
+        ControlMessage controlMessage = new ControlMessage(Constants.Flush);
+        byte[] byteArray = Encoding.UTF8.GetBytes(controlMessage.ToString());
+        SendMessage(byteArray);
+    }
+
+    /// <summary>
+    ///  This method Clears the text buffer on Deepgram to be converted to audio
+    ///  NOTE: These should never use the SendImmediately methods because they would by-pass the flow of text messages queued.
+    /// </summary>
+    public void Clear()
+    {
+        ControlMessage controlMessage = new ControlMessage(Constants.Clear);
+        byte[] byteArray = Encoding.UTF8.GetBytes(controlMessage.ToString());
+        SendMessage(byteArray);
+    }
+
+    /// <summary>
+    ///  This method tells Deepgram to initiate the close server-side.
+    ///  NOTE: This is fine to use the SendImmediately methods because you want to shutdown the websocket ASAP.
+    /// </summary>
+    public void Close(bool nullByte = false)
+    {
+        SendClose(nullByte).Wait();
+    }
+
+    /// <summary>
+    /// This method sends a close message over the WebSocket connection.
+    /// NOTE: This is fine to use the SendImmediately methods because you want to shutdown the websocket ASAP.
+    /// </summary>
+    public override async Task SendClose(bool nullByte = false)
+    {
+        if (_clientWebSocket == null || !IsConnected())
+        {
+            Log.Warning("SendClose", "ClientWebSocket is null or not connected. Skipping...");
+            return;
+        }
+
+        Log.Debug("SendClose", "Sending Close Message Immediately...");
+        if (nullByte)
+        {
+            // send a close to Deepgram
+            await _mutexSend.WaitAsync(_cancellationTokenSource.Token);
+            try
+            {
+                await _clientWebSocket.SendAsync(new ArraySegment<byte>(new byte[1] { 0 }), WebSocketMessageType.Binary, true, _cancellationTokenSource.Token)
+                    .ConfigureAwait(false);
+            }
+            finally
+            {
+                _mutexSend.Release();
+            }
+            return;
+        }
+
+        ControlMessage controlMessage = new ControlMessage(Constants.Close);
+        byte[] data = Encoding.UTF8.GetBytes(controlMessage.ToString());
+        await SendMessageImmediately(data);
+    }
+
+    /// <summary>
+    /// The SendMessage function needs to be overridden to handle the auto flush feature.
+    /// </summary>
+    public override void SendMessage(byte[] data, int length = Constants.UseArrayLengthForSend)
+    {
+        // auto flush
+        if (_deepgramClientOptions.InspectSpeakMessage())
+        {
+            string type = GetMessageType(data);
+            Log.Debug("SendMessage", $"Inspecting Message: Sending {type}");
+            switch (type)
+            {
+                case Constants.Flush:
+                    IncrementCounter().Wait();
+                    break;
+                case Constants.Speak:
+                    InspectMessage();
+                    break;
+            }
+        }
+
+        // send message
+        EnqueueSendMessage(new WebSocketMessage(data, WebSocketMessageType.Text, length));
+    }
+
+    /// <summary>
+    /// We need to override the Send function to use the SendMessage function. This is different than STT
+    /// because we only deal in text messages for TTS where STT is sending binary (or audio) messages.
+    /// </summary>
+    public override void Send(byte[] data, int length = Constants.UseArrayLengthForSend)
+    {
+        SendMessage(data, length);
+    }
+    #endregion
+
+    internal async Task ProcessAutoFlush()
+    {
+        Log.Verbose("LiveClient.ProcessAutoFlush", "ENTER");
+
+        var diffTicks = TimeSpan.FromMilliseconds((double)_deepgramClientOptions.AutoFlushSpeakDelta);
+
+        try
+        {
+            while (true)
+            {
+                Log.Verbose("ProcessAutoFlush", "Waiting for AutoFlush...");
+                await Task.Delay(Constants.DefaultFlushPeriodInMs, _cancellationTokenSource.Token);
+
+                if (_cancellationTokenSource.Token.IsCancellationRequested)
+                {
+                    Log.Information("ProcessAutoFlush", "ProcessAutoFlush cancelled");
+                    break;
+                }
+                if (!IsConnected())
+                {
+                    Log.Debug("ProcessAutoFlush", "WebSocket is not connected. Exiting...");
+                    return;
+                }
+
+                await _mutexLastDatagram.WaitAsync();
+                try
+                {
+                    if (_lastReceived == null)
+                    {
+                        Log.Debug("ProcessAutoFlush", "No datagram received. Skipping...");
+                        continue;
+                    }
+
+                    var deltaTicks = DateTime.Now - _lastReceived;
+                    if (deltaTicks < diffTicks)
+                    {
+                        Log.Debug("ProcessAutoFlush", $"AutoFlush delta is less than threshold: {deltaTicks}. Skipping...");
+                        continue;
+                    }
+
+                    Log.Debug("ProcessAutoFlush", $"AutoFlush delta exceeded threshold: {deltaTicks}. Skipping...");
+                    Flush();
+                    _lastReceived = null;
+                }
+                finally
+                {
+                    _mutexLastDatagram.Release();
+                }
+            }
+
+            Log.Verbose("ProcessAutoFlush", "Exit");
+            Log.Verbose("LiveClient.ProcessAutoFlush", "LEAVE");
+        }
+        catch (TaskCanceledException ex)
+        {
+            Log.Debug("ProcessAutoFlush", "KeepAliveThread cancelled.");
+            Log.Verbose("ProcessAutoFlush", $"KeepAliveThread cancelled. Info: {ex}");
+            Log.Verbose("LiveClient.ProcessAutoFlush", "LEAVE");
+        }
+        catch (Exception ex)
+        {
+            Log.Error("ProcessAutoFlush", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("ProcessAutoFlush", $"Excepton: {ex}");
+            Log.Verbose("LiveClient.ProcessAutoFlush", "LEAVE");
+        }
+    }
+
+    internal override void ProcessBinaryMessage(WebSocketReceiveResult result, MemoryStream ms)
+    {
+        try
+        {
+            Log.Debug("ProcessBinaryMessage", "Received WebSocketMessageType.Binary");
+
+            if (_audioReceived == null)
+            {
+                Log.Debug("ProcessBinaryMessage", "_audioReceived has no listeners");
+                Log.Verbose("ProcessBinaryMessage", "LEAVE");
+                return;
+            }
+
+            var audioResponse = new AudioResponse()
+            {
+                Stream = ms
+            };
+
+            Log.Debug("ProcessBinaryMessage", "Invoking AudioResponse");
+            InvokeParallel(_audioReceived, audioResponse);
+        }
+        catch (JsonException ex)
+        {
+            Log.Error("ProcessDataReceived", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("ProcessDataReceived", $"Exception: {ex}");
+            Log.Verbose("SpeakWSClient.ProcessDataReceived", "LEAVE");
+        }
+        catch (Exception ex)
+        {
+            Log.Error("ProcessDataReceived", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("ProcessDataReceived", $"Excepton: {ex}");
+            Log.Verbose("SpeakWSClient.ProcessDataReceived", "LEAVE");
+        }
+    }
+
+    internal override void ProcessTextMessage(WebSocketReceiveResult result, MemoryStream ms)
+    {
+        try
+        {
+            Log.Debug("ProcessDataReceived", "Received WebSocketMessageType.Text");
+
+            var response = Encoding.UTF8.GetString(ms.ToArray());
+            if (response == null)
+            {
+                Log.Warning("ProcessDataReceived", "Response is null");
+                Log.Verbose("SpeakWSClient.ProcessDataReceived", "LEAVE");
+                return;
+            }
+
+            Log.Verbose("ProcessDataReceived", $"raw response: {response}");
+            var data = JsonDocument.Parse(response);
+            var val = Enum.Parse(typeof(SpeakType), data.RootElement.GetProperty("type").GetString()!);
+
+            Log.Verbose("ProcessDataReceived", $"Type: {val}");
+
+            switch (val)
+            {
+                case SpeakType.Open:
+                case SpeakType.Close:
+                case SpeakType.Error:
+                    Log.Debug("ProcessTextMessage", "Calling base.ProcessTextMessage...");
+                    base.ProcessTextMessage(result, ms);
+                    break;
+                case SpeakType.Metadata:
+                    var metadataResponse = data.Deserialize<MetadataResponse>();
+                    if (_metadataReceived == null)
+                    {
+                        Log.Debug("ProcessDataReceived", "_metadataReceived has no listeners");
+                        Log.Verbose("ProcessDataReceived", "LEAVE");
+                        return;
+                    }
+                    if (metadataResponse == null)
+                    {
+                        Log.Warning("ProcessDataReceived", "MetadataResponse is invalid");
+                        Log.Verbose("ProcessDataReceived", "LEAVE");
+                        return;
+                    }
+
+                    Log.Debug("ProcessDataReceived", $"Invoking MetadataResponse. event: {metadataResponse}");
+                    InvokeParallel(_metadataReceived, metadataResponse);
+                    break;
+                case SpeakType.Flushed:
+                    var flushedResponse = data.Deserialize<FlushedResponse>();
+                    if (_flushedReceived == null)
+                    {
+                        Log.Debug("ProcessDataReceived", "_flushedReceived has no listeners");
+                        Log.Verbose("ProcessDataReceived", "LEAVE");
+                        return;
+                    }
+                    if (flushedResponse == null)
+                    {
+                        Log.Warning("ProcessDataReceived", "FlushedResponse is invalid");
+                        Log.Verbose("ProcessDataReceived", "LEAVE");
+                        return;
+                    }
+
+                    // auto flush
+                    if (_deepgramClientOptions.InspectSpeakMessage())
+                    {
+                        DecrementCounter().Wait();
+                    }
+
+                    Log.Debug("ProcessDataReceived", $"Invoking FlushedResponse. event: {flushedResponse}");
+                    InvokeParallel(_flushedReceived, flushedResponse);
+                    break;
+                case SpeakType.Cleared:
+                    var clearResponse = data.Deserialize<ClearedResponse>();
+                    if (_clearedReceived == null)
+                    {
+                        Log.Debug("ProcessDataReceived", "_clearedReceived has no listeners");
+                        Log.Verbose("ProcessDataReceived", "LEAVE");
+                        return;
+                    }
+                    if (clearResponse == null)
+                    {
+                        Log.Warning("ProcessDataReceived", "ClearedResponse is invalid");
+                        Log.Verbose("ProcessDataReceived", "LEAVE");
+                        return;
+                    }
+
+                    Log.Debug("ProcessDataReceived", $"Invoking ClearedResponse. event: {clearResponse}");
+                    InvokeParallel(_clearedReceived, clearResponse);
+                    break;
+                case SpeakType.Warning:
+                    var warningResponse = data.Deserialize<WarningResponse>();
+                    if (_warningReceived == null)
+                    {
+                        Log.Debug("ProcessDataReceived", "_warningReceived has no listeners");
+                        Log.Verbose("ProcessDataReceived", "LEAVE");
+                        return;
+                    }
+                    if (warningResponse == null)
+                    {
+                        Log.Warning("ProcessDataReceived", "WarningResponse is invalid");
+                        Log.Verbose("ProcessDataReceived", "LEAVE");
+                        return;
+                    }
+
+                    Log.Debug("ProcessDataReceived", $"Invoking WarningResponse. event: {warningResponse}");
+                    InvokeParallel(_warningReceived, warningResponse);
+                    break;
+                default:
+                    Log.Debug("ProcessTextMessage", "Calling base.ProcessTextMessage...");
+                    base.ProcessTextMessage(result, ms);
+                    break;
+            }
+        }
+        catch (JsonException ex)
+        {
+            Log.Error("ProcessDataReceived", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("ProcessDataReceived", $"Excepton: {ex}");
+            Log.Verbose("SpeakWSClient.ProcessDataReceived", "LEAVE");
+        }
+        catch (Exception ex)
+        {
+            Log.Error("ProcessDataReceived", $"{ex.GetType()} thrown {ex.Message}");
+            Log.Verbose("ProcessDataReceived", $"Excepton: {ex}");
+            Log.Verbose("SpeakWSClient.ProcessDataReceived", "LEAVE");
+        }
+    }
+
+    #region Helpers
+    /// <summary>
+    /// Get the URI for the WebSocket connection
+    /// </summary> 
+    internal static Uri GetUri(IDeepgramClientOptions options, SpeakSchema parameter, Dictionary<string, string>? addons = null)
+    {
+        var propertyInfoList = parameter.GetType()
+            .GetProperties()
+            .Where(v => v.GetValue(parameter) is not null);
+
+        var queryString = QueryParameterUtil.UrlEncode(parameter, propertyInfoList, addons);
+
+        return new Uri($"{options.BaseAddress}/{UriSegments.SPEAK}?{queryString}");
+    }
+
+    private async void InspectMessage()
+    {
+        Log.Verbose("InspectMessage", "ENTER");
+
+        if (_deepgramClientOptions.AutoFlushSpeakDelta > 0)
+        {
+            var now = DateTime.Now;
+            Log.Debug("InspectMessage", $"AutoFlush last received. Time: {now}");
+            await _mutexLastDatagram.WaitAsync();
+            try
+            {
+                _lastReceived = now;
+            }
+            finally
+            {
+                _mutexLastDatagram.Release();
+            }
+        }
+
+        Log.Debug("InspectMessage", "Succeeded");
+        Log.Verbose("InspectMessage", "LEAVE");
+    }
+
+    private async Task<bool> DecrementCounter()
+    {
+        await _mutexLastDatagram.WaitAsync();
+        try
+        {
+            _flushCount -= 1;
+            Log.Debug("DecrementCounter", $"Decrement Flush count: {_flushCount}");
+        }
+        finally
+        {
+            _mutexLastDatagram.Release();
+        }
+
+        return true;
+    }
+
+    private async Task<bool> IncrementCounter()
+    {
+        await _mutexLastDatagram.WaitAsync();
+        try
+        {
+            _flushCount += 1;
+            Log.Debug("IncrementCounter", $"Increment Flush count: {_flushCount}");
+        }
+        finally
+        {
+            _mutexLastDatagram.Release();
+        }
+
+        return true;
+    }
+
+    internal string GetMessageType(byte[] msg)
+    {
+        // Convert the byte array to a string
+        string response = Encoding.UTF8.GetString(msg);
+        if (response == null)
+        {
+            return "";
+        }
+
+        Log.Verbose("ProcessDataReceived", $"raw response: {response}");
+        var data = JsonDocument.Parse(response);
+
+        string val = data.RootElement.GetProperty("type").GetString() ?? "";
+        Log.Debug("ProcessDataReceived", $"Type: {val}");
+
+        return val;
+    }
+    #endregion
+}
diff --git a/Deepgram/Clients/Speak/v2/WebSocket/Constants.cs b/Deepgram/Clients/Speak/v2/WebSocket/Constants.cs
new file mode 100644
index 00000000..783d2c6f
--- /dev/null
+++ b/Deepgram/Clients/Speak/v2/WebSocket/Constants.cs
@@ -0,0 +1,28 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Clients.Speak.v2.WebSocket;
+
+/// <summary> 
+/// Headers of interest in the return values from the Deepgram Speak API.
+/// </summary> 
+public static class Constants
+{
+    // WS buffer size
+    public const int BufferSize = 1024 * 16;
+    public const int UseArrayLengthForSend = -1;
+
+    // Default timeout for connect/disconnect
+    public const int DefaultConnectTimeout = 5000;
+    public const int DefaultDisconnectTimeout = 5000;
+
+    public const int DefaultFlushPeriodInMs = 500;
+
+    // user message types
+    public const string Speak = "Speak";
+    public const string Flush = "Flush";
+    public const string Clear = "Clear";
+    public const string Close = "Close";
+}
+
diff --git a/Deepgram/Clients/Speak/v2/WebSocket/ResponseEvent.cs b/Deepgram/Clients/Speak/v2/WebSocket/ResponseEvent.cs
new file mode 100644
index 00000000..b4e249df
--- /dev/null
+++ b/Deepgram/Clients/Speak/v2/WebSocket/ResponseEvent.cs
@@ -0,0 +1,16 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Clients.Speak.v2.WebSocket;
+
+public class ResponseEvent<T> : EventArgs
+{
+    public T? Response { get; }
+
+    public ResponseEvent(T? response)
+    {
+        Response = response;
+    }
+}
+
diff --git a/Deepgram/Clients/Speak/v2/WebSocket/UriSegments.cs b/Deepgram/Clients/Speak/v2/WebSocket/UriSegments.cs
new file mode 100644
index 00000000..cc06799e
--- /dev/null
+++ b/Deepgram/Clients/Speak/v2/WebSocket/UriSegments.cs
@@ -0,0 +1,12 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Clients.Speak.v2.WebSocket;
+
+public static class UriSegments
+{
+    //using constants instead of inline value(magic strings) make consistence
+    //across SDK And Test Projects Simpler and Easier to change
+    public const string SPEAK = "speak";
+}
diff --git a/Deepgram/GlobalUsings.cs b/Deepgram/GlobalUsings.cs
index d3864d06..998aa8cb 100644
--- a/Deepgram/GlobalUsings.cs
+++ b/Deepgram/GlobalUsings.cs
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a MIT license that can be found in the LICENSE file.
 // SPDX-License-Identifier: MIT
 
-global using System.Collections.Concurrent;
 global using System.Net.Http.Headers;
 global using System.Net.WebSockets;
 global using System.Reflection;
@@ -12,7 +11,6 @@
 global using System.Text.RegularExpressions;
 global using System.Threading.Channels;
 global using System.Web;
-global using Deepgram.Abstractions;
 global using Deepgram.Constants;
 global using Deepgram.Logger;
 global using Deepgram.Utilities;
diff --git a/Deepgram/ListenWebSocketClient.cs b/Deepgram/ListenWebSocketClient.cs
index 09d69229..704bc27d 100644
--- a/Deepgram/ListenWebSocketClient.cs
+++ b/Deepgram/ListenWebSocketClient.cs
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT license that can be found in the LICENSE file.
 // SPDX-License-Identifier: MIT
 
-using Deepgram.Clients.Listen.v1.WebSocket;
+using Deepgram.Clients.Listen.v2.WebSocket;
 using Deepgram.Models.Authenticate.v1;
 
 namespace Deepgram;
diff --git a/Deepgram/Models/Common/v2/WebSocket/CloseResponse.cs b/Deepgram/Models/Common/v2/WebSocket/CloseResponse.cs
new file mode 100644
index 00000000..d34245eb
--- /dev/null
+++ b/Deepgram/Models/Common/v2/WebSocket/CloseResponse.cs
@@ -0,0 +1,37 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Common.v2.WebSocket;
+
+public record CloseResponse
+{
+    /// <summary>
+    /// Close event type.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("type")]
+    [JsonConverter(typeof(JsonStringEnumConverter))]
+    public WebSocketType? Type { get; set; } = WebSocketType.Close;
+
+    /// <summary>
+    /// Copy method to copy the object
+    /// </summary>
+    public void Copy(CloseResponse other)
+    {
+        if (other is null)
+        {
+            return;
+        }
+
+        Type = other.Type;
+    }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Common/v2/WebSocket/ErrorResponse.cs b/Deepgram/Models/Common/v2/WebSocket/ErrorResponse.cs
new file mode 100644
index 00000000..3ca5e705
--- /dev/null
+++ b/Deepgram/Models/Common/v2/WebSocket/ErrorResponse.cs
@@ -0,0 +1,61 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Common.v2.WebSocket;
+
+public record ErrorResponse
+{
+    /// <summary>
+    /// Error Description
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("description")]
+    public string? Description { get; set; } = "";
+
+    /// <summary>
+    /// Error Message
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("message")]
+    public string? Message { get; set; } = "";
+
+    /// <summary>
+    /// Error Variant
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("variant")]
+    public string? Variant { get; set; } = "";
+
+    /// <summary>
+    /// Error event type.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("type")]
+    [JsonConverter(typeof(JsonStringEnumConverter))]
+    public WebSocketType? Type { get; set; } = WebSocketType.Error;
+
+    /// <summary>
+    /// Copy method to copy the object
+    /// </summary>
+    public void Copy(ErrorResponse other)
+    {
+        if (other is null)
+        {
+            return;
+        }
+
+        Description = other.Description;
+        Message = other.Message;
+        Variant = other.Variant;
+        Type = other.Type;
+    }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Common/v2/WebSocket/OpenResponse.cs b/Deepgram/Models/Common/v2/WebSocket/OpenResponse.cs
new file mode 100644
index 00000000..67ad422e
--- /dev/null
+++ b/Deepgram/Models/Common/v2/WebSocket/OpenResponse.cs
@@ -0,0 +1,37 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Common.v2.WebSocket;
+
+public record OpenResponse
+{
+    /// <summary>
+    /// Open event type.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("type")]
+    [JsonConverter(typeof(JsonStringEnumConverter))]
+    public WebSocketType? Type { get; set; } = WebSocketType.Open;
+
+    /// <summary>
+    /// Copy method to copy the object
+    /// </summary>
+    public void Copy(OpenResponse other)
+    {
+        if (other is null)
+        {
+            return;
+        }
+
+        Type = other.Type;
+    }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Common/v2/WebSocket/UnhandledResponse.cs b/Deepgram/Models/Common/v2/WebSocket/UnhandledResponse.cs
new file mode 100644
index 00000000..502fa036
--- /dev/null
+++ b/Deepgram/Models/Common/v2/WebSocket/UnhandledResponse.cs
@@ -0,0 +1,45 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Common.v2.WebSocket;
+
+public record UnhandledResponse 
+{
+    /// <summary>
+    /// Raw JSON
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("raw")]
+    public string? Raw { get; set; } = "";
+
+    /// <summary>
+    /// Unhandled event type.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("type")]
+    [JsonConverter(typeof(JsonStringEnumConverter))]
+    public WebSocketType? Type { get; set; } = WebSocketType.Unhandled;
+
+    /// <summary>
+    /// Copy method to copy the object
+    /// </summary>
+    public void Copy(UnhandledResponse other)
+    {
+        if (other == null)
+        {
+            return;
+        }
+
+        Raw = other.Raw;
+        Type = other.Type;
+    }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Common/v2/WebSocket/WebSocketType.cs b/Deepgram/Models/Common/v2/WebSocket/WebSocketType.cs
new file mode 100644
index 00000000..1825b190
--- /dev/null
+++ b/Deepgram/Models/Common/v2/WebSocket/WebSocketType.cs
@@ -0,0 +1,13 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Common.v2.WebSocket;
+
+public enum WebSocketType
+{
+    Open,
+    Close,
+    Unhandled,
+    Error,
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/Alternative.cs b/Deepgram/Models/Listen/v2/WebSocket/Alternative.cs
new file mode 100644
index 00000000..0a59fb61
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/Alternative.cs
@@ -0,0 +1,43 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+public record Alternative
+{
+    /// <summary>
+    /// Single-string transcript containing what the model hears in this channel of audio.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("transcript")]
+    public string? Transcript { get; set; }
+    /// <summary>
+    /// Value between 0 and 1 indicating the model's relative confidence in this transcript.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("confidence")]
+    public double? Confidence { get; set; }
+
+    /// <summary>
+    /// ReadOnly List of <see cref="Word"/> objects.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("words")]
+    public IReadOnlyList<Word>? Words { get; set; }
+
+    /// <summary>
+    /// ReadOnlyList of Languages Detected
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+    [JsonPropertyName("languages")]
+    public IReadOnlyList<string>? Languages { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/Average.cs b/Deepgram/Models/Listen/v2/WebSocket/Average.cs
new file mode 100644
index 00000000..3809bb74
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/Average.cs
@@ -0,0 +1,30 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+public record Average
+{
+    /// <summary>
+    /// Sentiment: Positive, Negative, or Neutral.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("sentiment")]
+    public string? Sentiment { get; set; }
+
+    /// <summary>
+    /// Sentiment score.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("sentiment_score")]
+    public double? SentimentScore { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/Channel.cs b/Deepgram/Models/Listen/v2/WebSocket/Channel.cs
new file mode 100644
index 00000000..51fab15d
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/Channel.cs
@@ -0,0 +1,30 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+public record Channel
+{
+    /// <summary>
+    /// ReadOnlyList of <see cref="Alternative"/> objects.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("alternatives")]
+    public IReadOnlyList<Alternative>? Alternatives { get; set; }
+
+    /// <summary>
+    /// ReadOnlyList of Search objects.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+    [JsonPropertyName("search")]
+    public IReadOnlyList<Search>? Search { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/CloseResponse.cs b/Deepgram/Models/Listen/v2/WebSocket/CloseResponse.cs
new file mode 100644
index 00000000..ec7c50a8
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/CloseResponse.cs
@@ -0,0 +1,11 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+using Common = Deepgram.Models.Common.v2.WebSocket;
+
+public record CloseResponse : Common.CloseResponse
+{
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/ErrorResponse.cs b/Deepgram/Models/Listen/v2/WebSocket/ErrorResponse.cs
new file mode 100644
index 00000000..e90e7cf3
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/ErrorResponse.cs
@@ -0,0 +1,11 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+using Common = Deepgram.Models.Common.v2.WebSocket;
+
+public record ErrorResponse : Common.ErrorResponse
+{
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/Hit.cs b/Deepgram/Models/Listen/v2/WebSocket/Hit.cs
new file mode 100644
index 00000000..4f6dedc7
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/Hit.cs
@@ -0,0 +1,45 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+public record Hit
+{
+    /// <summary>
+    /// Value between 0 and 1 that indicates the model's relative confidence in this hit.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("confidence")]
+    public double? Confidence { get; set; }
+
+
+    /// <summary>
+    /// Offset in seconds from the start of the audio to where the hit ends.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("end")]
+    public decimal? End { get; set; }
+
+    /// <summary>
+    /// Transcript that corresponds to the time between start and end.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("snippet")]
+    public string? Snippet { get; set; }
+
+    /// <summary>
+    /// Offset in seconds from the start of the audio to where the hit occurs.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("start")]
+    public decimal? Start { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/ListenType.cs b/Deepgram/Models/Listen/v2/WebSocket/ListenType.cs
new file mode 100644
index 00000000..5f2744d2
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/ListenType.cs
@@ -0,0 +1,19 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+using Deepgram.Models.Common.v2.WebSocket;
+
+public enum ListenType
+{
+    Open = WebSocketType.Open,
+    Close = WebSocketType.Close,
+    Unhandled = WebSocketType.Unhandled,
+    Error = WebSocketType.Error,
+    Metadata,
+    Results,
+    UtteranceEnd,
+    SpeechStarted,
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/LiveSchema.cs b/Deepgram/Models/Listen/v2/WebSocket/LiveSchema.cs
new file mode 100644
index 00000000..67fe0e82
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/LiveSchema.cs
@@ -0,0 +1,262 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+public class LiveSchema
+{
+
+    /// <summary>
+    /// Number of transcripts to return per request
+    /// <see href="https://developers.deepgram.com/reference/pre-recorded"/>
+    /// Default is 1
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("alternatives")]
+    public int? Alternatives { get; set; }
+
+    /// <summary>
+    /// CallBack allows you to have your submitted audio processed asynchronously.
+    /// <see href="https://developers.deepgram.com/docs/callback">
+    /// default is null
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("callback")]
+    public string? CallBack { get; set; }
+
+    /// <summary>
+    /// Enables callback method
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("callback_method")]
+    public bool? CallbackMethod { get; set; }
+
+    /// <summary>
+    /// Channels allows you to specify the number of independent audio channels your submitted audio contains. 
+    /// Used when the Encoding feature is also being used to submit streaming raw audio
+    /// <see href="https://developers.deepgram.com/docs/channels">
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("channels")]
+    public int? Channels { get; set; }
+
+    /// <summary>
+    /// Diarize recognizes speaker changes and assigns a speaker to each word in the transcript. 
+    /// <see href="https://developers.deepgram.com/docs/diarization">
+    /// default is false
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("diarize")]
+    public bool? Diarize { get; set; }
+
+    // <summary>
+    /// <see href="https://developers.deepgram.com/docs/diarization">
+    /// default is null, only applies if Diarize is set to true
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("diarize_version")]
+    public string? DiarizeVersion { get; set; }
+
+    /// <summary>
+    /// Dictation is a feature of Deepgram’s Speech-to-Text API that converts spoken dictation commands into their corresponding punctuation marks. 
+    /// <see href="https://developers.deepgram.com/docs/dictation">
+    /// default is false
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+    [JsonPropertyName("dictation")]
+    public bool? Dictation { get; set; }
+
+    /// <summary>
+    /// Encoding allows you to specify the expected encoding of your submitted audio.
+    /// <see href="https://developers.deepgram.com/docs/encoding">
+    /// supported encodings <see cref="AudioEncoding"/>
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("encoding")]
+    public string? Encoding { get; set; }
+
+    /// <summary>
+    /// Endpointing returns transcripts when pauses in speech are detected.
+    /// <see href="https://developers.deepgram.com/docs/endpointing">
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("endpointing")]
+    public string? EndPointing { get; set; }
+
+    /// <summary>
+    /// Deepgram’s Extra Metadata feature allows you to attach arbitrary key-value pairs to your API requests that are attached to the API response for usage in downstream processing.
+    /// Extra metadata is limited to 2048 characters per key-value pair.
+    /// <see href="https://developers.deepgram.com/docs/extra-metadata"/>
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("extra")]
+    public Dictionary<string, string>? Extra { get; set; }
+
+    /// <summary>
+    /// Whether to include words like "uh" and "um" in transcription output. 
+    ///<see href="https://developers.deepgram.com/reference/pre-recorded"/>
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("filler_words")]
+    public bool? FillerWords { get; set; }
+
+    /// <summary>
+    /// Interim Results provides preliminary results for streaming audio to solve the need for immediate results combined with high levels of accuracy.
+    /// <see href="https://developers.deepgram.com/docs/interim-results">
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("interim_results")]
+    public bool? InterimResults { get; set; }
+
+    /// <summary>
+    /// Keywords can boost or suppress specialized terminology.
+    /// <see href="https://developers.deepgram.com/docs/keywords">
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("keywords")]
+    public List<string>? Keywords { get; set; }
+
+    /// <summary>
+    /// Primary spoken language of submitted audio 
+    /// <see href="https://developers.deepgram.com/docs/language">
+    /// default value is 'en' 
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("language")]
+    public string? Language { get; set; }
+
+    /// <summary>
+    /// AI model used to process submitted audio
+    /// <see href="https://developers.deepgram.com/docs/model">
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("model")]
+    public string? Model { get; set; }
+
+    /// <summary>
+    /// Multichannel transcribes each channel in submitted audio independently. 
+    /// <see href="https://developers.deepgram.com/docs/multichannel">
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("multichannel")]
+    public bool? MultiChannel { get; set; }
+
+    /// <summary>
+    /// Enables No Delay
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+    [JsonPropertyName("no_delay")]
+    public bool? NoDelay { get; set; }
+
+    /// <summary>
+    /// Numerals converts numbers from written format to numerical format.
+    /// <see href="https://developers.deepgram.com/docs/numerals">
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("numerals")]
+    public bool? Numerals { get; set; }
+
+    /// <summary>
+    /// Profanity Filter looks for recognized profanity and converts it to the nearest recognized 
+    /// non-profane word or removes it from the transcript completely.
+    /// <see href="https://developers.deepgram.com/docs/profanity-filter">
+    /// for use with base model tier only
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("profanity_filter")]
+    public bool? ProfanityFilter { get; set; }
+
+    /// <summary>
+    /// Adds punctuation and capitalization to transcript
+    /// <see href="https://developers.deepgram.com/docs/punctuation">
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("punctuate")]
+    public bool? Punctuate { get; set; }
+
+    /// <summary>
+    ///  Indicates whether to redact sensitive information, replacing redacted content with asterisks (*). Can send multiple instances in query string (for example, redact=pci&redact=numbers).
+    ///  <see href="https://developers.deepgram.com/docs/redaction">
+    ///  default is List<string>("false") 
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("redact")]
+    public List<string>? Redact { get; set; }
+
+    /// <summary>
+    /// Find and Replace searches for terms or phrases in submitted audio and replaces them.
+    /// <see href="https://developers.deepgram.com/docs/find-and-replace">
+    /// default is null
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("replace")]
+    public List<string>? Replace { get; set; }
+
+    /// <summary>
+    /// Sample Rate allows you to specify the sample rate of your submitted audio.
+    /// <see href="https://developers.deepgram.com/docs/sample-rate">
+    /// only applies when Encoding has a value
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("sample_rate")]
+    public int? SampleRate { get; set; }
+
+    /// <summary>
+    /// Search searches for terms or phrases in submitted audio. 
+    /// <see href="https://developers.deepgram.com/docs/search">
+    /// default is null
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("search")]
+    public List<string>? Search { get; set; }
+
+    /// <summary>
+    /// Smart Format formats transcripts to improve readability. 
+    /// <see href="https://developers.deepgram.com/docs/smart-format">
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("smart_format")]
+    public bool? SmartFormat { get; set; }
+
+    /// <summary>
+    /// Tagging allows you to label your requests with one or more tags in a list,for the purpose of identification during usage reporting.
+    /// <see href="https://developers.deepgram.com/docs/tagging">
+    /// Default is a null 
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("tag")]
+    public List<string>? Tag { get; set; }
+
+    /// <summary>
+    /// Indicates how long Deepgram will wait to send a {"type": "UtteranceEnd"} message after a word has been transcribed
+    /// <see href="https://developers.deepgram.com/docs/understanding-end-of-speech-detection-while-streaming"/>
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("utterance_end_ms")]
+    public string? UtteranceEnd { get; set; }
+
+    /// <summary>
+    /// Enables voice activity detection (VAD) events
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("vad_events")]
+    public bool? VadEvents { get; set; }
+
+    /// <summary>
+    /// Version of the model to use.
+    /// <see href="https://developers.deepgram.com/docs/version">
+    /// default value is "latest"
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("version")]
+    public string? Version { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/Metadata.cs b/Deepgram/Models/Listen/v2/WebSocket/Metadata.cs
new file mode 100644
index 00000000..9ace5968
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/Metadata.cs
@@ -0,0 +1,46 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+public record MetaData
+{
+    /// <summary>
+    /// The request ID is a unique identifier for the request. It is useful for troubleshooting and support.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("request_id")]
+    public string? RequestId { get; set; }
+
+    /// <summary>
+    /// Model UUID
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("model_uuid")]
+    public string? ModelUUID { get; set; }
+
+    /// <summary>
+    /// IReadonlyDictionary of <see cref="ModelInfo"/>
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("model_info")]
+    public ModelInfo? ModelInfo { get; set; }
+
+    /// <summary>
+    /// Deepgram’s Extra Metadata feature allows you to attach arbitrary key-value pairs to your API requests that are attached to the API response for usage in downstream processing.
+    /// Extra metadata is limited to 2048 characters per key-value pair.
+    /// <see href="https://developers.deepgram.com/docs/extra-metadata"/>
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("extra")]
+    public Dictionary<string, string>? Extra { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/MetadataResponse.cs b/Deepgram/Models/Listen/v2/WebSocket/MetadataResponse.cs
new file mode 100644
index 00000000..170aea34
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/MetadataResponse.cs
@@ -0,0 +1,89 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+public record MetadataResponse
+{
+    /// <summary>
+    /// Channel count
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("channels")]
+    public int? Channels { get; set; }
+
+    /// <summary>
+    /// Created date/time
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("created")]
+    public DateTime? Created { get; set; }
+
+    /// <summary>
+    /// Duration of the audio
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("duration")]
+    public double? Duration { get; set; }
+
+    /// <summary>
+    /// Model Information
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("model_info")]
+    public IReadOnlyDictionary<string, ModelInfo>? ModelInfo { get; set; }
+
+    /// <summary>
+    /// Models used containing UUIDs
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("models")]
+    public IReadOnlyList<string>? Models { get; set; }
+
+    /// <summary>
+    /// Request ID is a unique identifier for the request. It is useful for troubleshooting and support.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("request_id")]
+    public string? RequestId { get; set; }
+
+    /// <summary>
+    /// Sha256 information
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("sha256")]
+    public string? Sha256 { get; set; }
+
+    /// <summary>
+    /// (Obsolete?) his field is only present if the request was made with a transaction key.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("transaction_key")]
+    public string? TransactionKey { get; set; }
+
+    /// <summary>
+    /// Metadata event type.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("type")]
+    [JsonConverter(typeof(JsonStringEnumConverter))]
+    public ListenType? Type { get; set; } = ListenType.Metadata;
+
+    /// <summary>
+    /// Deepgram’s Extra Metadata feature allows you to attach arbitrary key-value pairs to your API requests that are attached to the API response for usage in downstream processing.
+    /// Extra metadata is limited to 2048 characters per key-value pair.
+    /// <see href="https://developers.deepgram.com/docs/extra-metadata"/>
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("extra")]
+    public Dictionary<string, string>? Extra { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/ModelInfo.cs b/Deepgram/Models/Listen/v2/WebSocket/ModelInfo.cs
new file mode 100644
index 00000000..4bd44eb6
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/ModelInfo.cs
@@ -0,0 +1,37 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+public record ModelInfo
+{
+    /// <summary>
+    /// Architecture of the model
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("arch")]
+    public string? Arch { get; set; }
+
+    /// <summary>
+    /// Name of the model
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("name")]
+    public string? Name { get; set; }
+
+    /// <summary>
+    /// Version of the model
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("version")]
+    public string? Version { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/OpenResponse.cs b/Deepgram/Models/Listen/v2/WebSocket/OpenResponse.cs
new file mode 100644
index 00000000..e0e65a60
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/OpenResponse.cs
@@ -0,0 +1,11 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+using Common = Deepgram.Models.Common.v2.WebSocket;
+
+public record OpenResponse : Common.OpenResponse
+{
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/ResultResponse.cs b/Deepgram/Models/Listen/v2/WebSocket/ResultResponse.cs
new file mode 100644
index 00000000..e55ed77d
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/ResultResponse.cs
@@ -0,0 +1,86 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+public record ResultResponse
+{
+    /// <summary>
+    /// Contains the channel information.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("channel")]
+    public Channel? Channel { get; set; }
+
+    /// <summary>
+    /// Channel index.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("channel_index")]
+    public IReadOnlyList<int>? ChannelIndex { get; set; }
+
+    /// <summary>
+    /// Duration of the result.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("duration")]
+    public decimal? Duration { get; set; }
+
+    /// <summary>
+    /// Is the result final.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("is_final")]
+    public bool? IsFinal { get; set; } = false;
+
+    /// <summary>
+    /// Indicates whether this result was generated during the finalization process.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+    [JsonPropertyName("from_finalize")]
+    public bool? FromFinalize { get; set; }
+
+    /// <summary>
+    /// Metadata information.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("metadata")]
+    public MetaData? MetaData { get; set; }
+
+    /// <summary>
+    /// Is the result a partial result.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("speech_final")]
+    public bool? SpeechFinal { get; set; }
+
+    /// <summary>
+    /// Start time of the result.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("start")]
+    public decimal? Start { get; set; }
+
+    /// <summary>
+    /// Result event type.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("type")]
+    [JsonConverter(typeof(JsonStringEnumConverter))]
+    public ListenType? Type { get; set; } = ListenType.Results;
+
+    // TODO: DYV is this needed???
+    /// <summary>
+    /// Error information.
+    /// </summary>
+    public Exception? Error { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/Search.cs b/Deepgram/Models/Listen/v2/WebSocket/Search.cs
new file mode 100644
index 00000000..d003e816
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/Search.cs
@@ -0,0 +1,31 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+public record Search
+{
+    /// <summary>
+    /// Term for which Deepgram is searching.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("query")]
+    public string? Query { get; set; }
+
+    /// <summary>
+    /// ReadonlyList of <see cref="Hit"/>
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("hits")]
+    public IReadOnlyList<Hit>? Hits { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
+
diff --git a/Deepgram/Models/Listen/v2/WebSocket/SpeechStartedResponse.cs b/Deepgram/Models/Listen/v2/WebSocket/SpeechStartedResponse.cs
new file mode 100644
index 00000000..568410c2
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/SpeechStartedResponse.cs
@@ -0,0 +1,38 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+public record SpeechStartedResponse
+{
+    /// <summary>
+    /// SpeechStarted event type.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("type")]
+    [JsonConverter(typeof(JsonStringEnumConverter))]
+    public ListenType? Type { get; set; } = ListenType.SpeechStarted;
+
+    /// <summary>
+    /// Channel index information
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("channel_index")]
+    public int[]? Channel { get; set; }
+
+    /// <summary>
+    /// Timestamp of the event.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("timestamp")]
+    public decimal? Timestamp { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/UnhandledResponse.cs b/Deepgram/Models/Listen/v2/WebSocket/UnhandledResponse.cs
new file mode 100644
index 00000000..72895792
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/UnhandledResponse.cs
@@ -0,0 +1,11 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+using Common = Deepgram.Models.Common.v2.WebSocket;
+
+public record UnhandledResponse : Common.UnhandledResponse
+{
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/UtteranceEndResponse.cs b/Deepgram/Models/Listen/v2/WebSocket/UtteranceEndResponse.cs
new file mode 100644
index 00000000..947c5d6d
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/UtteranceEndResponse.cs
@@ -0,0 +1,38 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+public record UtteranceEndResponse
+{
+    /// <summary>
+    /// Utterance end event type.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("type")]
+    [JsonConverter(typeof(JsonStringEnumConverter))]
+    public ListenType? Type { get; set; } = ListenType.UtteranceEnd;
+
+    /// <summary>
+    /// Channel index information
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("channel_index")]
+    public int[]? Channel { get; set; }
+
+    /// <summary>
+    /// Timestamp of the event.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("last_word_end")]
+    public decimal? LastWordEnd { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Listen/v2/WebSocket/Word.cs b/Deepgram/Models/Listen/v2/WebSocket/Word.cs
new file mode 100644
index 00000000..55ebe3ca
--- /dev/null
+++ b/Deepgram/Models/Listen/v2/WebSocket/Word.cs
@@ -0,0 +1,65 @@
+// Copyright 2021-2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Listen.v2.WebSocket;
+
+public record Word
+{
+    /// <summary>
+    /// Distinct word heard by the model.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("word")]
+    public string? HeardWord { get; set; }
+
+    /// <summary>
+    /// Offset in seconds from the start of the audio to where the spoken word starts.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("start")]
+    public decimal? Start { get; set; }
+
+    /// <summary>
+    /// Offset in seconds from the start of the audio to where the spoken word ends.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("end")]
+    public decimal? End { get; set; }
+
+    /// <summary>
+    /// Value between 0 and 1 indicating the model's relative confidence in this word.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("confidence")]
+    public double? Confidence { get; set; }
+
+    /// <summary>
+    /// Punctuated version of the word
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("punctuated_word")]
+    public string? PunctuatedWord { get; set; }
+
+    /// <summary>
+    /// Language detected
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+    [JsonPropertyName("language")]
+    public string? Language { get; set; }
+
+    /// <summary>
+    /// Speaker index of who said this word
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("speaker")]
+    public int? Speaker { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Speak/v1/WebSocket/AudioResponse.cs b/Deepgram/Models/Speak/v1/WebSocket/AudioResponse.cs
index 0f58ff32..051ae506 100644
--- a/Deepgram/Models/Speak/v1/WebSocket/AudioResponse.cs
+++ b/Deepgram/Models/Speak/v1/WebSocket/AudioResponse.cs
@@ -7,7 +7,7 @@ namespace Deepgram.Models.Speak.v1.WebSocket;
 public record AudioResponse : IDisposable
 {
     /// <summary>
-    /// Open event type.
+    /// The type of speak response, defaults to Audio.
     /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
 	[JsonPropertyName("type")]
diff --git a/Deepgram/Models/Speak/v1/WebSocket/ControlMessage.cs b/Deepgram/Models/Speak/v1/WebSocket/ControlMessage.cs
index 7d67d049..046b2c3e 100644
--- a/Deepgram/Models/Speak/v1/WebSocket/ControlMessage.cs
+++ b/Deepgram/Models/Speak/v1/WebSocket/ControlMessage.cs
@@ -7,7 +7,7 @@ namespace Deepgram.Models.Speak.v1.WebSocket;
 public class ControlMessage(string text)
 {
     /// <summary>
-    /// Text of the words to speak
+    /// Gets or sets the type of control message.
     /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("type")]
diff --git a/Deepgram/Models/Speak/v2/WebSocket/AudioResponse.cs b/Deepgram/Models/Speak/v2/WebSocket/AudioResponse.cs
new file mode 100644
index 00000000..a66f7941
--- /dev/null
+++ b/Deepgram/Models/Speak/v2/WebSocket/AudioResponse.cs
@@ -0,0 +1,30 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Speak.v2.WebSocket;
+
+public record AudioResponse : IDisposable
+{
+    /// <summary>
+    /// The type of speak response, defaults to Audio.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("type")]
+    [JsonConverter(typeof(JsonStringEnumConverter))]
+    public SpeakType? Type { get; set; } = SpeakType.Audio;
+
+    /// <summary>
+    /// A stream of the audio file
+    /// </summary>
+    public MemoryStream? Stream { get; set; }
+
+    // NOTE: There isn't a ToString() function because this will cause an odd Exception to be thrown:
+    // InvalidOperationException: "Timeouts are not supported on this stream."
+
+    public void Dispose()
+    {
+        Stream?.Dispose();
+        Stream = null;
+    }
+}
diff --git a/Deepgram/Models/Speak/v2/WebSocket/ClearedResponse.cs b/Deepgram/Models/Speak/v2/WebSocket/ClearedResponse.cs
new file mode 100644
index 00000000..3974e0d5
--- /dev/null
+++ b/Deepgram/Models/Speak/v2/WebSocket/ClearedResponse.cs
@@ -0,0 +1,31 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Speak.v2.WebSocket;
+
+public record ClearedResponse
+{
+    /// <summary>
+    /// Clear event type.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("type")]
+    [JsonConverter(typeof(JsonStringEnumConverter))]
+    public SpeakType? Type { get; set; } = SpeakType.Cleared;
+
+    /// <summary>
+    /// Sequence ID
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+    [JsonPropertyName("sequence_id")]
+    public int? SequenceId { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Speak/v2/WebSocket/CloseResponse.cs b/Deepgram/Models/Speak/v2/WebSocket/CloseResponse.cs
new file mode 100644
index 00000000..7f6009dd
--- /dev/null
+++ b/Deepgram/Models/Speak/v2/WebSocket/CloseResponse.cs
@@ -0,0 +1,11 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Speak.v2.WebSocket;
+
+using Common = Deepgram.Models.Common.v2.WebSocket;
+
+public record CloseResponse : Common.CloseResponse
+{
+}
diff --git a/Deepgram/Models/Speak/v2/WebSocket/ControlMessage.cs b/Deepgram/Models/Speak/v2/WebSocket/ControlMessage.cs
new file mode 100644
index 00000000..f5b5e806
--- /dev/null
+++ b/Deepgram/Models/Speak/v2/WebSocket/ControlMessage.cs
@@ -0,0 +1,24 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Speak.v2.WebSocket;
+
+public class ControlMessage(string text)
+{
+    /// <summary>
+    /// Gets or sets the type of control message.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+    [JsonPropertyName("type")]
+    public string? Type { get; set; } = text;
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
+
diff --git a/Deepgram/Models/Speak/v2/WebSocket/ErrorResponse.cs b/Deepgram/Models/Speak/v2/WebSocket/ErrorResponse.cs
new file mode 100644
index 00000000..ad3b8c28
--- /dev/null
+++ b/Deepgram/Models/Speak/v2/WebSocket/ErrorResponse.cs
@@ -0,0 +1,11 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Speak.v2.WebSocket;
+
+using Common = Deepgram.Models.Common.v2.WebSocket;
+
+public record ErrorResponse : Common.ErrorResponse
+{
+}
diff --git a/Deepgram/Models/Speak/v2/WebSocket/FlushedResponse.cs b/Deepgram/Models/Speak/v2/WebSocket/FlushedResponse.cs
new file mode 100644
index 00000000..f94f54ac
--- /dev/null
+++ b/Deepgram/Models/Speak/v2/WebSocket/FlushedResponse.cs
@@ -0,0 +1,31 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Speak.v2.WebSocket;
+
+public record FlushedResponse
+{
+    /// <summary>
+    /// Flush event type.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("type")]
+    [JsonConverter(typeof(JsonStringEnumConverter))]
+    public SpeakType? Type { get; set; } = SpeakType.Flushed;
+
+    /// <summary>
+    /// Sequence ID
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+    [JsonPropertyName("sequence_id")]
+    public int? SequenceId { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Speak/v2/WebSocket/MetadataResponse.cs b/Deepgram/Models/Speak/v2/WebSocket/MetadataResponse.cs
new file mode 100644
index 00000000..d0a4b9c8
--- /dev/null
+++ b/Deepgram/Models/Speak/v2/WebSocket/MetadataResponse.cs
@@ -0,0 +1,31 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Speak.v2.WebSocket;
+
+public record MetadataResponse
+{
+    /// <summary>
+    /// Request ID is a unique identifier for the request. It is useful for troubleshooting and support.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("request_id")]
+    public string? RequestId { get; set; }
+
+    /// <summary>
+    /// Metadata event type.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("type")]
+    [JsonConverter(typeof(JsonStringEnumConverter))]
+    public SpeakType? Type { get; set; } = SpeakType.Metadata;
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Speak/v2/WebSocket/OpenResponse.cs b/Deepgram/Models/Speak/v2/WebSocket/OpenResponse.cs
new file mode 100644
index 00000000..6b01feef
--- /dev/null
+++ b/Deepgram/Models/Speak/v2/WebSocket/OpenResponse.cs
@@ -0,0 +1,11 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Speak.v2.WebSocket;
+
+using Common = Deepgram.Models.Common.v2.WebSocket;
+
+public record OpenResponse : Common.OpenResponse
+{
+}
diff --git a/Deepgram/Models/Speak/v2/WebSocket/SpeakSchema.cs b/Deepgram/Models/Speak/v2/WebSocket/SpeakSchema.cs
new file mode 100644
index 00000000..4b519aea
--- /dev/null
+++ b/Deepgram/Models/Speak/v2/WebSocket/SpeakSchema.cs
@@ -0,0 +1,56 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Speak.v2.WebSocket;
+
+public class SpeakSchema
+{
+    /// <summary>
+    /// AI model used to process submitted audio
+    /// <see href="https://developers.deepgram.com/docs/model">
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+    [JsonPropertyName("model")]
+    public string? Model { get; set; } = "aura-asteria-en";
+
+    /// <summary>
+    /// Bit Rate allows you to specify the bit rate of your desired audio.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("bit_rate")]
+    public int? BitRate { get; set; }
+
+    ///// <summary>
+    ///// Audio container format
+    ///// </summary>
+    //[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+    //[JsonPropertyName("container")]
+    //public string? Container { get; set; }
+
+    /// <summary>
+    /// Encoding allows you to specify the expected encoding of your submitted audio.
+    /// <see href="https://developers.deepgram.com/docs/encoding">
+    /// supported encodings <see cref="AudioEncoding"/>
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("encoding")]
+    public string? Encoding { get; set; }
+
+    /// <summary>
+    /// Sample Rate allows you to specify the sample rate of your submitted audio.
+    /// <see href="https://developers.deepgram.com/docs/sample-rate">
+    /// Only applies when Encoding has a value
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("sample_rate")]
+    public int? SampleRate { get; set; }
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/Models/Speak/v2/WebSocket/SpeakType.cs b/Deepgram/Models/Speak/v2/WebSocket/SpeakType.cs
new file mode 100644
index 00000000..4eb36a5f
--- /dev/null
+++ b/Deepgram/Models/Speak/v2/WebSocket/SpeakType.cs
@@ -0,0 +1,21 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Models.Common.v2.WebSocket;
+
+namespace Deepgram.Models.Speak.v2.WebSocket;
+
+public enum SpeakType
+{
+    Open = WebSocketType.Open,
+    Close = WebSocketType.Close,
+    Unhandled = WebSocketType.Unhandled,
+    Error = WebSocketType.Error,
+    Metadata,
+    Flushed,
+    Cleared,
+    Reset,
+    Audio,
+    Warning,
+}
diff --git a/Deepgram/Models/Speak/v2/WebSocket/TextSource.cs b/Deepgram/Models/Speak/v2/WebSocket/TextSource.cs
new file mode 100644
index 00000000..559fa9f1
--- /dev/null
+++ b/Deepgram/Models/Speak/v2/WebSocket/TextSource.cs
@@ -0,0 +1,31 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Speak.v2.WebSocket;
+
+public class TextSource(string text)
+{
+    /// <summary>
+    /// Text of the words to speak
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+    [JsonPropertyName("type")]
+    public string? Type { get; set; } = "Speak";
+
+    /// <summary>
+    /// Text of the words to speak
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+    [JsonPropertyName("text")]
+    public string? Text { get; set; } = text;
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
+
diff --git a/Deepgram/Models/Speak/v2/WebSocket/UnhandledResponse.cs b/Deepgram/Models/Speak/v2/WebSocket/UnhandledResponse.cs
new file mode 100644
index 00000000..a1cb3117
--- /dev/null
+++ b/Deepgram/Models/Speak/v2/WebSocket/UnhandledResponse.cs
@@ -0,0 +1,11 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Speak.v2.WebSocket;
+
+using Common = Deepgram.Models.Common.v2.WebSocket;
+
+public record UnhandledResponse : Common.UnhandledResponse
+{
+}
diff --git a/Deepgram/Models/Speak/v2/WebSocket/WarningResponse.cs b/Deepgram/Models/Speak/v2/WebSocket/WarningResponse.cs
new file mode 100644
index 00000000..d08b1ab1
--- /dev/null
+++ b/Deepgram/Models/Speak/v2/WebSocket/WarningResponse.cs
@@ -0,0 +1,45 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+namespace Deepgram.Models.Speak.v2.WebSocket;
+
+public record WarningResponse
+{
+    /// <summary>
+    /// Error Description
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("warn_code")]
+    public string? WarnCode { get; set; } = "";
+
+    /// <summary>
+    /// Error Message
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("warn_msg")]
+    public string? WarnMsg { get; set; } = "";
+
+    /// <summary>
+    /// Error Variant
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("variant")]
+    public string? Variant { get; set; } = "";
+
+    /// <summary>
+    /// Error event type.
+    /// </summary>
+    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+	[JsonPropertyName("type")]
+    [JsonConverter(typeof(JsonStringEnumConverter))]
+    public SpeakType? Type { get; set; } = SpeakType.Warning;
+
+    /// <summary>
+    /// Override ToString method to serialize the object
+    /// </summary>
+    public override string ToString()
+    {
+        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+    }
+}
diff --git a/Deepgram/SpeakWebSocketClient.cs b/Deepgram/SpeakWebSocketClient.cs
index a6e184c5..de8c204e 100644
--- a/Deepgram/SpeakWebSocketClient.cs
+++ b/Deepgram/SpeakWebSocketClient.cs
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT license that can be found in the LICENSE file.
 // SPDX-License-Identifier: MIT
 
-using Deepgram.Clients.Speak.v1.WebSocket;
+using Deepgram.Clients.Speak.v2.WebSocket;
 using Deepgram.Models.Authenticate.v1;
 
 namespace Deepgram;
diff --git a/examples/speech-to-text/websocket/file/Program.cs b/examples/speech-to-text/websocket/file/Program.cs
index 04f2609f..b105cfab 100644
--- a/examples/speech-to-text/websocket/file/Program.cs
+++ b/examples/speech-to-text/websocket/file/Program.cs
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT license that can be found in the LICENSE file.
 // SPDX-License-Identifier: MIT
 
-using Deepgram.Models.Listen.v1.WebSocket;
+using Deepgram.Models.Listen.v2.WebSocket;
 
 namespace SampleApp
 {
@@ -10,46 +10,58 @@ class Program
     {
         static async Task Main(string[] args)
         {
-            // Initialize Library with default logging
-            // Normal logging is "Info" level
-            Library.Initialize();
+            try
+            {
+                // Initialize Library with default logging
+                // Normal logging is "Info" level
+                Library.Initialize();
 
-            // use the client factory with a API Key set with the "DEEPGRAM_API_KEY" environment variable
-            var liveClient = ClientFactory.CreateListenWebSocketClient();
+                // use the client factory with a API Key set with the "DEEPGRAM_API_KEY" environment variable
+                var liveClient = ClientFactory.CreateListenWebSocketClient();
 
-            // Subscribe to the EventResponseReceived event
-            liveClient.Subscribe(new EventHandler<ResultResponse>((sender, e) =>
-            {
-                if (e.Channel.Alternatives[0].Transcript == "")
+                // Subscribe to the EventResponseReceived event
+                await liveClient.Subscribe(new EventHandler<ResultResponse>((sender, e) =>
                 {
-                    return;
-                }
+                    if (e.Channel.Alternatives[0].Transcript == "")
+                    {
+                        return;
+                    }
 
-                // Console.WriteLine("Transcription received: " + JsonSerializer.Serialize(e.Transcription));
-                Console.WriteLine($"\n\n\nSpeaker: {e.Channel.Alternatives[0].Transcript}\n\n\n");
-            }));
+                    // Console.WriteLine("Transcription received: " + JsonSerializer.Serialize(e.Transcription));
+                    Console.WriteLine($"\n\n\nSpeaker: {e.Channel.Alternatives[0].Transcript}\n\n\n");
+                }));
 
-            // Start the connection
-            var liveSchema = new LiveSchema()
-            {
-                Model = "nova-2",
-                Punctuate = true,
-                SmartFormat = true,
-            };
-            await liveClient.Connect(liveSchema);
+                // Start the connection
+                var liveSchema = new LiveSchema()
+                {
+                    Model = "nova-2",
+                    Punctuate = true,
+                    SmartFormat = true,
+                };
+                bool bConnected = await liveClient.Connect(liveSchema);
+                if (!bConnected)
+                {
+                    Console.WriteLine("Failed to connect to the server");
+                    return;
+                }
 
-            // Send some audio data
-            var audioData = File.ReadAllBytes(@"preamble.wav");
-            liveClient.Send(audioData);
+                // Send some audio data
+                var audioData = File.ReadAllBytes(@"preamble.wav");
+                liveClient.Send(audioData);
 
-            // Wait for a while to receive responses
-            await Task.Delay(45000);
+                // Wait for a while to receive responses
+                await Task.Delay(45000);
 
-            // Stop the connection
-            await liveClient.Stop();
+                // Stop the connection
+                await liveClient.Stop();
 
-            // Teardown Library
-            Library.Terminate();
+                // Teardown Library
+                Library.Terminate();
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Exception: {ex.Message}");
+            }
         }
     }
 }
diff --git a/examples/speech-to-text/websocket/http/Program.cs b/examples/speech-to-text/websocket/http/Program.cs
index 7767d4df..75f67889 100644
--- a/examples/speech-to-text/websocket/http/Program.cs
+++ b/examples/speech-to-text/websocket/http/Program.cs
@@ -2,7 +2,8 @@
 // Use of this source code is governed by a MIT license that can be found in the LICENSE file.
 // SPDX-License-Identifier: MIT
 
-using Deepgram.Models.Listen.v1.WebSocket;
+using Deepgram.Models.Listen.v2.WebSocket;
+using System.Linq.Expressions;
 
 namespace SampleApp
 {
@@ -10,58 +11,70 @@ class Program
     {
         static async Task Main(string[] args)
         {
-            // Initialize Library with default logging
-            Library.Initialize();
+            try
+            {
+                // Initialize Library with default logging
+                Library.Initialize();
 
-            // use the client factory with a API Key set with the "DEEPGRAM_API_KEY" environment variable
-            var liveClient = new ListenWebSocketClient();
+                // use the client factory with a API Key set with the "DEEPGRAM_API_KEY" environment variable
+                var liveClient = new ListenWebSocketClient();
 
-            // Subscribe to the EventResponseReceived event
-            liveClient.Subscribe(new EventHandler<ResultResponse>((sender, e) =>
-            {
-                if (e.Channel.Alternatives[0].Transcript == "")
+                // Subscribe to the EventResponseReceived event
+                await liveClient.Subscribe(new EventHandler<ResultResponse>((sender, e) =>
                 {
+                    if (e.Channel.Alternatives[0].Transcript == "")
+                    {
+                        return;
+                    }
+                    Console.WriteLine($"Speaker: {e.Channel.Alternatives[0].Transcript}");
+                }));
+
+                // Start the connection
+                var liveSchema = new LiveSchema()
+                {
+                    Model = "nova-2",
+                    Punctuate = true,
+                    SmartFormat = true,
+                };
+                bool bConnected = await liveClient.Connect(liveSchema);
+                if (!bConnected)
+                {
+                    Console.WriteLine("Failed to connect to the server");
                     return;
                 }
-                Console.WriteLine($"Speaker: {e.Channel.Alternatives[0].Transcript}");
-            }));
-
-            // Start the connection
-            var liveSchema = new LiveSchema()
-            {
-                Model = "nova-2",
-                Punctuate = true,
-                SmartFormat = true,
-            };
-            await liveClient.Connect(liveSchema);
 
-            // get the webcast data... this is a blocking operation
-            try
-            {
-                var url = "http://stream.live.vc.bbcmedia.co.uk/bbc_world_service";
-                using (HttpClient client = new HttpClient())
+                // get the webcast data... this is a blocking operation
+                try
                 {
-                    using (Stream receiveStream = await client.GetStreamAsync(url))
+                    var url = "http://stream.live.vc.bbcmedia.co.uk/bbc_world_service";
+                    using (HttpClient client = new HttpClient())
                     {
-                        while (liveClient.IsConnected())
+                        using (Stream receiveStream = await client.GetStreamAsync(url))
                         {
-                            byte[] buffer = new byte[2048];
-                            await receiveStream.ReadAsync(buffer, 0, buffer.Length);
-                            liveClient.Send(buffer);
+                            while (liveClient.IsConnected())
+                            {
+                                byte[] buffer = new byte[2048];
+                                await receiveStream.ReadAsync(buffer, 0, buffer.Length);
+                                liveClient.Send(buffer);
+                            }
                         }
                     }
                 }
+                catch (Exception e)
+                {
+                    Console.WriteLine(e.Message);
+                }
+
+                // Stop the connection
+                await liveClient.Stop();
+
+                // Teardown Library
+                Library.Terminate();
             }
             catch (Exception e)
             {
                 Console.WriteLine(e.Message);
             }
-
-            // Stop the connection
-            await liveClient.Stop();
-
-            // Teardown Library
-            Library.Terminate();
         }
     }
 }
diff --git a/examples/speech-to-text/websocket/microphone/Program.cs b/examples/speech-to-text/websocket/microphone/Program.cs
index 08f73e76..f0a4c1c5 100644
--- a/examples/speech-to-text/websocket/microphone/Program.cs
+++ b/examples/speech-to-text/websocket/microphone/Program.cs
@@ -5,7 +5,7 @@
 using Deepgram.Logger;
 using Deepgram.Microphone;
 using Deepgram.Models.Authenticate.v1;
-using Deepgram.Models.Listen.v1.WebSocket;
+using Deepgram.Models.Listen.v2.WebSocket;
 
 namespace SampleApp
 {
@@ -13,93 +13,105 @@ class Program
     {
         static async Task Main(string[] args)
         {
-            // Initialize Library with default logging
-            // Normal logging is "Info" level
-            Deepgram.Library.Initialize();
-            // OR very chatty logging
-            //Deepgram.Library.Initialize(LogLevel.Debug); // LogLevel.Default, LogLevel.Debug, LogLevel.Verbose
+            try
+            {
+                // Initialize Library with default logging
+                // Normal logging is "Info" level
+                //Deepgram.Library.Initialize();
+                // OR very chatty logging
+                Deepgram.Library.Initialize(LogLevel.Verbose); // LogLevel.Default, LogLevel.Debug, LogLevel.Verbose
 
-            // Initialize the microphone library
-            Deepgram.Microphone.Library.Initialize();
+                // Initialize the microphone library
+                Deepgram.Microphone.Library.Initialize();
 
-            Console.WriteLine("\n\nPress any key to stop and exit...\n\n\n");
+                Console.WriteLine("\n\nPress any key to stop and exit...\n\n\n");
 
-            // Set "DEEPGRAM_API_KEY" environment variable to your Deepgram API Key
-            DeepgramWsClientOptions options = new DeepgramWsClientOptions(null, null, true);
-            //options.AutoFlushReplyDelta = 2000; // if your live stream application is like "push to talk".
-            var liveClient = ClientFactory.CreateListenWebSocketClient(apiKey: "", options: options);
+                // Set "DEEPGRAM_API_KEY" environment variable to your Deepgram API Key
+                DeepgramWsClientOptions options = new DeepgramWsClientOptions(null, null, true);
+                //options.AutoFlushReplyDelta = 2000; // if your live stream application is like "push to talk".
+                var liveClient = ClientFactory.CreateListenWebSocketClient(apiKey: "", options: options);
 
-            // Subscribe to the EventResponseReceived event
-            liveClient.Subscribe(new EventHandler<OpenResponse>((sender, e) =>
-            {
-                Console.WriteLine($"\n\n----> {e.Type} received");
-            }));
-            liveClient.Subscribe(new EventHandler<MetadataResponse>((sender, e) =>
-            {
-                Console.WriteLine($"----> {e.Type} received");
-            }));
-            liveClient.Subscribe(new EventHandler<ResultResponse>((sender, e) =>
-            {
-                Console.WriteLine($"----> {e.Type} received");
-                if (e.Channel.Alternatives[0].Transcript.Trim() == "")
+                // Subscribe to the EventResponseReceived event
+                await liveClient.Subscribe(new EventHandler<OpenResponse>((sender, e) =>
                 {
-                    return;
-                }
+                    Console.WriteLine($"----> {e.Type} received");
+                }));
+                await liveClient.Subscribe(new EventHandler<MetadataResponse>((sender, e) =>
+                {
+                    Console.WriteLine($"----> {e.Type} received");
+                }));
+                await liveClient.Subscribe(new EventHandler<ResultResponse>((sender, e) =>
+                {
+                    Console.WriteLine($"----> {e.Type} received");
+                    if (e.Channel.Alternatives[0].Transcript.Trim() == "")
+                    {
+                        return;
+                    }
 
-                // Console.WriteLine("Transcription received: " + JsonSerializer.Serialize(e.Transcription));
-                Console.WriteLine($"----> Speaker: {e.Channel.Alternatives[0].Transcript}");
-            }));
-            liveClient.Subscribe(new EventHandler<SpeechStartedResponse>((sender, e) =>
-            {
-                Console.WriteLine($"----> {e.Type} received");
-            }));
-            liveClient.Subscribe(new EventHandler<UtteranceEndResponse>((sender, e) =>
-            {
-                Console.WriteLine($"----> {e.Type} received");
-            }));
-            liveClient.Subscribe(new EventHandler<CloseResponse>((sender, e) =>
-            {
-                Console.WriteLine($"----> {e.Type} received");
-            }));
-            liveClient.Subscribe(new EventHandler<UnhandledResponse>((sender, e) =>
-            {
-                Console.WriteLine($"----> {e.Type} received");
-            }));
-            liveClient.Subscribe(new EventHandler<ErrorResponse>((sender, e) =>
-            {
-                Console.WriteLine($"----> {e.Type} received. Error: {e.Message}");
-            }));
+                    // Console.WriteLine("Transcription received: " + JsonSerializer.Serialize(e.Transcription));
+                    Console.WriteLine($"----> Speaker: {e.Channel.Alternatives[0].Transcript}");
+                }));
+                await liveClient.Subscribe(new EventHandler<SpeechStartedResponse>((sender, e) =>
+                {
+                    Console.WriteLine($"----> {e.Type} received");
+                }));
+                await liveClient.Subscribe(new EventHandler<UtteranceEndResponse>((sender, e) =>
+                {
+                    Console.WriteLine($"----> {e.Type} received");
+                }));
+                await liveClient.Subscribe(new EventHandler<CloseResponse>((sender, e) =>
+                {
+                    Console.WriteLine($"----> {e.Type} received");
+                }));
+                await liveClient.Subscribe(new EventHandler<UnhandledResponse>((sender, e) =>
+                {
+                    Console.WriteLine($"----> {e.Type} received");
+                }));
+                await liveClient.Subscribe(new EventHandler<ErrorResponse>((sender, e) =>
+                {
+                    Console.WriteLine($"----> {e.Type} received. Error: {e.Message}");
+                }));
 
-            // Start the connection
-            var liveSchema = new LiveSchema()
-            {
-                Model = "nova-2",
-                Encoding = "linear16",
-                SampleRate = 16000,
-                Punctuate = true,
-                SmartFormat = true,
-                InterimResults = true,
-                UtteranceEnd = "1000",
-                VadEvents = true,
-            };
-            await liveClient.Connect(liveSchema);
+                // Start the connection
+                var liveSchema = new LiveSchema()
+                {
+                    Model = "nova-2",
+                    Encoding = "linear16",
+                    SampleRate = 16000,
+                    Punctuate = true,
+                    SmartFormat = true,
+                    InterimResults = true,
+                    UtteranceEnd = "1000",
+                    VadEvents = true,
+                };
+                bool bConnected = await liveClient.Connect(liveSchema);
+                if (!bConnected)
+                {
+                    Console.WriteLine("Failed to connect to Deepgram WebSocket server.");
+                    return;
+                }
 
-            // Microphone streaming
-            var microphone = new Microphone(liveClient.Send);
-            microphone.Start();
+                // Microphone streaming
+                var microphone = new Microphone(liveClient.Send);
+                microphone.Start();
 
-            // Wait for the user to press a key
-            Console.ReadKey();
+                // Wait for the user to press a key
+                Console.ReadKey();
 
-            // Stop the microphone
-            microphone.Stop();
+                // Stop the microphone
+                microphone.Stop();
 
-            // Stop the connection
-            await liveClient.Stop();
+                // Stop the connection
+                await liveClient.Stop();
 
-            // Terminate Libraries
-            Deepgram.Microphone.Library.Terminate();
-            Deepgram.Library.Terminate();
+                // Terminate Libraries
+                Deepgram.Microphone.Library.Terminate();
+                Deepgram.Library.Terminate();
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Exception: {ex.Message}");
+            }
         }
     }
 }
diff --git a/examples/text-to-speech/websocket/simple/Program.cs b/examples/text-to-speech/websocket/simple/Program.cs
index 89f37448..712bca87 100644
--- a/examples/text-to-speech/websocket/simple/Program.cs
+++ b/examples/text-to-speech/websocket/simple/Program.cs
@@ -2,10 +2,8 @@
 // Use of this source code is governed by a MIT license that can be found in the LICENSE file.
 // SPDX-License-Identifier: MIT
 
-using System.Text;
-
 using Deepgram.Models.Authenticate.v1;
-using Deepgram.Models.Speak.v1.WebSocket;
+using Deepgram.Models.Speak.v2.WebSocket;
 using Deepgram.Logger;
 
 
@@ -15,154 +13,166 @@ class Program
     {
         static async Task Main(string[] args)
         {
-            // Initialize Library with default logging
-            // Normal logging is "Info" level
-            //Library.Initialize();
-            // OR very chatty logging
-            Library.Initialize(LogLevel.Verbose); // LogLevel.Default, LogLevel.Debug, LogLevel.Verbose
-
-            //// use the client factory with a API Key set with the "DEEPGRAM_API_KEY" environment variable
-            //DeepgramWsClientOptions options = new DeepgramWsClientOptions(null, "ENTER URL HERE");
-            //options.AutoFlushSpeakDelta = 1000;
-            //var speakClient = ClientFactory.CreateSpeakWebSocketClient("", options);
-            var speakClient = ClientFactory.CreateSpeakWebSocketClient();
-
-            // append wav header only once
-            bool appendWavHeader = true;
-
-            // Subscribe to the EventResponseReceived event
-            speakClient.Subscribe(new EventHandler<OpenResponse>((sender, e) =>
-            {
-                Console.WriteLine($"\n\n----> {e.Type} received");
-            }));
-            speakClient.Subscribe(new EventHandler<MetadataResponse>((sender, e) =>
+            try
             {
-                Console.WriteLine($"----> {e.Type} received");
-                Console.WriteLine($"----> RequestId: {e.RequestId}");
-            }));
-            speakClient.Subscribe(new EventHandler<AudioResponse>((sender, e) =>
-            {
-                Console.WriteLine($"----> {e.Type} received");
-
-                // add a wav header
-                if (appendWavHeader)
+                // Initialize Library with default logging
+                // Normal logging is "Info" level
+                //Library.Initialize();
+                // OR very chatty logging
+                Library.Initialize(LogLevel.Verbose); // LogLevel.Default, LogLevel.Debug, LogLevel.Verbose
+
+                //// use the client factory with a API Key set with the "DEEPGRAM_API_KEY" environment variable
+                //DeepgramWsClientOptions options = new DeepgramWsClientOptions();
+                //options.AutoFlushSpeakDelta = 1000;
+                //var speakClient = ClientFactory.CreateSpeakWebSocketClient("", options);
+                var speakClient = ClientFactory.CreateSpeakWebSocketClient();
+
+                // append wav header only once
+                bool appendWavHeader = true;
+
+                // Subscribe to the EventResponseReceived event
+                await speakClient.Subscribe(new EventHandler<OpenResponse>((sender, e) =>
                 {
-                    using (BinaryWriter writer = new BinaryWriter(File.Open("output.wav", FileMode.Append)))
+                    Console.WriteLine($"\n\n----> {e.Type} received");
+                }));
+                await speakClient.Subscribe(new EventHandler<MetadataResponse>((sender, e) =>
+                {
+                    Console.WriteLine($"----> {e.Type} received");
+                    Console.WriteLine($"----> RequestId: {e.RequestId}");
+                }));
+                await speakClient.Subscribe(new EventHandler<AudioResponse>((sender, e) =>
+                {
+                    Console.WriteLine($"----> {e.Type} received");
+
+                    // add a wav header
+                    if (appendWavHeader)
                     {
-                        Console.WriteLine("Adding WAV header to output.wav");
-                        byte[] wavHeader = new byte[44];
-                        int sampleRate = 48000;
-                        short bitsPerSample = 16;
-                        short channels = 1;
-                        int byteRate = sampleRate * channels * (bitsPerSample / 8);
-                        short blockAlign = (short)(channels * (bitsPerSample / 8));
-
-                        wavHeader[0] = 0x52; // R
-                        wavHeader[1] = 0x49; // I
-                        wavHeader[2] = 0x46; // F
-                        wavHeader[3] = 0x46; // F
-                        wavHeader[4] = 0x00; // Placeholder for file size (will be updated later)
-                        wavHeader[5] = 0x00; // Placeholder for file size (will be updated later)
-                        wavHeader[6] = 0x00; // Placeholder for file size (will be updated later)
-                        wavHeader[7] = 0x00; // Placeholder for file size (will be updated later)
-                        wavHeader[8] = 0x57; // W
-                        wavHeader[9] = 0x41; // A
-                        wavHeader[10] = 0x56; // V
-                        wavHeader[11] = 0x45; // E
-                        wavHeader[12] = 0x66; // f
-                        wavHeader[13] = 0x6D; // m
-                        wavHeader[14] = 0x74; // t
-                        wavHeader[15] = 0x20; // Space
-                        wavHeader[16] = 0x10; // Subchunk1Size (16 for PCM)
-                        wavHeader[17] = 0x00; // Subchunk1Size
-                        wavHeader[18] = 0x00; // Subchunk1Size
-                        wavHeader[19] = 0x00; // Subchunk1Size
-                        wavHeader[20] = 0x01; // AudioFormat (1 for PCM)
-                        wavHeader[21] = 0x00; // AudioFormat
-                        wavHeader[22] = (byte)channels; // NumChannels
-                        wavHeader[23] = 0x00; // NumChannels
-                        wavHeader[24] = (byte)(sampleRate & 0xFF); // SampleRate
-                        wavHeader[25] = (byte)((sampleRate >> 8) & 0xFF); // SampleRate
-                        wavHeader[26] = (byte)((sampleRate >> 16) & 0xFF); // SampleRate
-                        wavHeader[27] = (byte)((sampleRate >> 24) & 0xFF); // SampleRate
-                        wavHeader[28] = (byte)(byteRate & 0xFF); // ByteRate
-                        wavHeader[29] = (byte)((byteRate >> 8) & 0xFF); // ByteRate
-                        wavHeader[30] = (byte)((byteRate >> 16) & 0xFF); // ByteRate
-                        wavHeader[31] = (byte)((byteRate >> 24) & 0xFF); // ByteRate
-                        wavHeader[32] = (byte)blockAlign; // BlockAlign
-                        wavHeader[33] = 0x00; // BlockAlign
-                        wavHeader[34] = (byte)bitsPerSample; // BitsPerSample
-                        wavHeader[35] = 0x00; // BitsPerSample
-                        wavHeader[36] = 0x64; // d
-                        wavHeader[37] = 0x61; // a
-                        wavHeader[38] = 0x74; // t
-                        wavHeader[39] = 0x61; // a
-                        wavHeader[40] = 0x00; // Placeholder for data chunk size (will be updated later)
-                        wavHeader[41] = 0x00; // Placeholder for data chunk size (will be updated later)
-                        wavHeader[42] = 0x00; // Placeholder for data chunk size (will be updated later)
-                        wavHeader[43] = 0x00; // Placeholder for data chunk size (will be updated later)
-
-                        writer.Write(wavHeader);
-                        appendWavHeader = false;
+                        using (BinaryWriter writer = new BinaryWriter(File.Open("output.wav", FileMode.Append)))
+                        {
+                            Console.WriteLine("Adding WAV header to output.wav");
+                            byte[] wavHeader = new byte[44];
+                            int sampleRate = 48000;
+                            short bitsPerSample = 16;
+                            short channels = 1;
+                            int byteRate = sampleRate * channels * (bitsPerSample / 8);
+                            short blockAlign = (short)(channels * (bitsPerSample / 8));
+
+                            wavHeader[0] = 0x52; // R
+                            wavHeader[1] = 0x49; // I
+                            wavHeader[2] = 0x46; // F
+                            wavHeader[3] = 0x46; // F
+                            wavHeader[4] = 0x00; // Placeholder for file size (will be updated later)
+                            wavHeader[5] = 0x00; // Placeholder for file size (will be updated later)
+                            wavHeader[6] = 0x00; // Placeholder for file size (will be updated later)
+                            wavHeader[7] = 0x00; // Placeholder for file size (will be updated later)
+                            wavHeader[8] = 0x57; // W
+                            wavHeader[9] = 0x41; // A
+                            wavHeader[10] = 0x56; // V
+                            wavHeader[11] = 0x45; // E
+                            wavHeader[12] = 0x66; // f
+                            wavHeader[13] = 0x6D; // m
+                            wavHeader[14] = 0x74; // t
+                            wavHeader[15] = 0x20; // Space
+                            wavHeader[16] = 0x10; // Subchunk1Size (16 for PCM)
+                            wavHeader[17] = 0x00; // Subchunk1Size
+                            wavHeader[18] = 0x00; // Subchunk1Size
+                            wavHeader[19] = 0x00; // Subchunk1Size
+                            wavHeader[20] = 0x01; // AudioFormat (1 for PCM)
+                            wavHeader[21] = 0x00; // AudioFormat
+                            wavHeader[22] = (byte)channels; // NumChannels
+                            wavHeader[23] = 0x00; // NumChannels
+                            wavHeader[24] = (byte)(sampleRate & 0xFF); // SampleRate
+                            wavHeader[25] = (byte)((sampleRate >> 8) & 0xFF); // SampleRate
+                            wavHeader[26] = (byte)((sampleRate >> 16) & 0xFF); // SampleRate
+                            wavHeader[27] = (byte)((sampleRate >> 24) & 0xFF); // SampleRate
+                            wavHeader[28] = (byte)(byteRate & 0xFF); // ByteRate
+                            wavHeader[29] = (byte)((byteRate >> 8) & 0xFF); // ByteRate
+                            wavHeader[30] = (byte)((byteRate >> 16) & 0xFF); // ByteRate
+                            wavHeader[31] = (byte)((byteRate >> 24) & 0xFF); // ByteRate
+                            wavHeader[32] = (byte)blockAlign; // BlockAlign
+                            wavHeader[33] = 0x00; // BlockAlign
+                            wavHeader[34] = (byte)bitsPerSample; // BitsPerSample
+                            wavHeader[35] = 0x00; // BitsPerSample
+                            wavHeader[36] = 0x64; // d
+                            wavHeader[37] = 0x61; // a
+                            wavHeader[38] = 0x74; // t
+                            wavHeader[39] = 0x61; // a
+                            wavHeader[40] = 0x00; // Placeholder for data chunk size (will be updated later)
+                            wavHeader[41] = 0x00; // Placeholder for data chunk size (will be updated later)
+                            wavHeader[42] = 0x00; // Placeholder for data chunk size (will be updated later)
+                            wavHeader[43] = 0x00; // Placeholder for data chunk size (will be updated later)
+
+                            writer.Write(wavHeader);
+                            appendWavHeader = false;
+                        }
                     }
-                }
 
-                if (e.Stream != null)
-                {
-                    using (BinaryWriter writer = new BinaryWriter(File.Open("output.wav", FileMode.Append)))
+                    if (e.Stream != null)
                     {
-                        writer.Write(e.Stream.ToArray());
+                        using (BinaryWriter writer = new BinaryWriter(File.Open("output.wav", FileMode.Append)))
+                        {
+                            writer.Write(e.Stream.ToArray());
+                        }
                     }
-                }
-            }));
-            speakClient.Subscribe(new EventHandler<FlushedResponse>((sender, e) =>
-            {
-                Console.WriteLine($"----> {e.Type} received");
-            }));
-            speakClient.Subscribe(new EventHandler<ClearedResponse>((sender, e) =>
-            {
-                Console.WriteLine($"----> {e.Type} received");
-            }));
-            speakClient.Subscribe(new EventHandler<CloseResponse>((sender, e) =>
-            {
-                Console.WriteLine($"----> {e.Type} received");
-            }));
-            speakClient.Subscribe(new EventHandler<UnhandledResponse>((sender, e) =>
-            {
-                Console.WriteLine($"----> {e.Type} received");
-            }));
-            speakClient.Subscribe(new EventHandler<WarningResponse>((sender, e) =>
-            {
-                Console.WriteLine($"----> {e.Type} received");
-            }));
-            speakClient.Subscribe(new EventHandler<ErrorResponse>((sender, e) =>
-            {
-                Console.WriteLine($"----> {e.Type} received. Error: {e.Message}");
-            }));
+                }));
+                await speakClient.Subscribe(new EventHandler<FlushedResponse>((sender, e) =>
+                {
+                    Console.WriteLine($"----> {e.Type} received");
+                }));
+                await speakClient.Subscribe(new EventHandler<ClearedResponse>((sender, e) =>
+                {
+                    Console.WriteLine($"----> {e.Type} received");
+                }));
+                await speakClient.Subscribe(new EventHandler<CloseResponse>((sender, e) =>
+                {
+                    Console.WriteLine($"----> {e.Type} received");
+                }));
+                await speakClient.Subscribe(new EventHandler<UnhandledResponse>((sender, e) =>
+                {
+                    Console.WriteLine($"----> {e.Type} received");
+                }));
+                await speakClient.Subscribe(new EventHandler<WarningResponse>((sender, e) =>
+                {
+                    Console.WriteLine($"----> {e.Type} received");
+                }));
+                await speakClient.Subscribe(new EventHandler<ErrorResponse>((sender, e) =>
+                {
+                    Console.WriteLine($"----> {e.Type} received. Error: {e.Message}");
+                }));
 
-            // Start the connection
-            var speakSchema = new SpeakSchema()
-            {
-                Encoding = "linear16",
-                SampleRate = 48000
-            };
-            await speakClient.Connect(speakSchema);
+                // Start the connection
+                var speakSchema = new SpeakSchema()
+                {
+                    Encoding = "linear16",
+                    SampleRate = 48000,
+                };
+                bool bConnected = await speakClient.Connect(speakSchema);
+                if (!bConnected)
+                {
+                    Console.WriteLine("Failed to connect to the server");
+                    return;
+                }
 
-            // Send some Text to convert to audio
-            speakClient.SpeakWithText("Hello World!");
+                // Send some Text to convert to audio
+                speakClient.SpeakWithText("Hello World!");
 
-            //Flush the audio
-            speakClient.Flush();
+                //Flush the audio
+                speakClient.Flush();
 
-            // Wait for the user to press a key
-            Console.WriteLine("\n\nPress any key to stop and exit...\n\n\n");
-            Console.ReadKey();
+                // Wait for the user to press a key
+                Console.WriteLine("\n\nPress any key to stop and exit...\n\n\n");
+                Console.ReadKey();
 
-            // Stop the connection
-            await speakClient.Stop();
+                // Stop the connection
+                await speakClient.Stop();
 
-            // Terminate Libraries
-            Library.Terminate();
+                // Terminate Libraries
+                Library.Terminate();
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Exception: {ex.Message}");
+            }
         }
     }
 }
diff --git a/tests/edge_cases/keepalive/Program.cs b/tests/edge_cases/keepalive/Program.cs
index e60393af..d343a90c 100644
--- a/tests/edge_cases/keepalive/Program.cs
+++ b/tests/edge_cases/keepalive/Program.cs
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: MIT
 
 using Deepgram.Models.Authenticate.v1;
-using Deepgram.Models.Listen.v1.WebSocket;
+using Deepgram.Models.Listen.v2.WebSocket;
 using Deepgram.Logger;
 
 namespace SampleApp
@@ -25,11 +25,11 @@ static async Task Main(string[] args)
             var liveClient = ClientFactory.CreateListenWebSocketClient("", options);
 
             // Subscribe to the EventResponseReceived event
-            liveClient.Subscribe(new EventHandler<OpenResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<OpenResponse>((sender, e) =>
             {
                 Console.WriteLine($"----> {e.Type} received");
             }));
-            liveClient.Subscribe(new EventHandler<ResultResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<ResultResponse>((sender, e) =>
             {
                 if (e.Channel.Alternatives[0].Transcript == "")
                 {
@@ -39,11 +39,11 @@ static async Task Main(string[] args)
                 // Console.WriteLine("Transcription received: " + JsonSerializer.Serialize(e.Transcription));
                 Console.WriteLine($"\n\n\n----> Speaker: {e.Channel.Alternatives[0].Transcript}\n\n\n");
             }));
-            liveClient.Subscribe(new EventHandler<CloseResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<CloseResponse>((sender, e) =>
             {
                 Console.WriteLine($"----> {e.Type} received");
             }));
-            liveClient.Subscribe(new EventHandler<ErrorResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<ErrorResponse>((sender, e) =>
             {
                 Console.WriteLine($"----> {e.Type} received. Error: {e.Message}");
             }));
diff --git a/tests/edge_cases/reconnect_same_object/Program.cs b/tests/edge_cases/reconnect_same_object/Program.cs
index d7639d95..4ebf5825 100644
--- a/tests/edge_cases/reconnect_same_object/Program.cs
+++ b/tests/edge_cases/reconnect_same_object/Program.cs
@@ -5,7 +5,7 @@
 using Deepgram.Logger;
 using Deepgram.Microphone;
 using Deepgram.Models.Authenticate.v1;
-using Deepgram.Models.Listen.v1.WebSocket;
+using Deepgram.Models.Listen.v2.WebSocket;
 
 namespace SampleApp
 {
@@ -29,15 +29,15 @@ static async Task Main(string[] args)
             //var liveClient = new LiveClienkt("set your DEEPGRAM_API_KEY here");
 
             // Subscribe to the EventResponseReceived event
-            liveClient.Subscribe(new EventHandler<OpenResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<OpenResponse>((sender, e) =>
             {
                 Console.WriteLine($"\n\n----> {e.Type} received");
             }));
-            liveClient.Subscribe(new EventHandler<MetadataResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<MetadataResponse>((sender, e) =>
             {
                 Console.WriteLine($"----> {e.Type} received");
             }));
-            liveClient.Subscribe(new EventHandler<ResultResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<ResultResponse>((sender, e) =>
             {
                 Console.WriteLine($"----> {e.Type} received");
                 if (e.Channel.Alternatives[0].Transcript.Trim() == "")
@@ -48,23 +48,23 @@ static async Task Main(string[] args)
                 // Console.WriteLine("Transcription received: " + JsonSerializer.Serialize(e.Transcription));
                 Console.WriteLine($"----> Speaker: {e.Channel.Alternatives[0].Transcript}");
             }));
-            liveClient.Subscribe(new EventHandler<SpeechStartedResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<SpeechStartedResponse>((sender, e) =>
             {
                 Console.WriteLine($"----> {e.Type} received");
             }));
-            liveClient.Subscribe(new EventHandler<UtteranceEndResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<UtteranceEndResponse>((sender, e) =>
             {
                 Console.WriteLine($"----> {e.Type} received");
             }));
-            liveClient.Subscribe(new EventHandler<CloseResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<CloseResponse>((sender, e) =>
             {
                 Console.WriteLine($"----> {e.Type} received");
             }));
-            liveClient.Subscribe(new EventHandler<UnhandledResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<UnhandledResponse>((sender, e) =>
             {
                 Console.WriteLine($"----> {e.Type} received");
             }));
-            liveClient.Subscribe(new EventHandler<ErrorResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<ErrorResponse>((sender, e) =>
             {
                 Console.WriteLine($"----> {e.Type} received. Error: {e.Message}");
             }));
diff --git a/tests/edge_cases/stt_v1_client_example/Program.cs b/tests/edge_cases/stt_v1_client_example/Program.cs
new file mode 100644
index 00000000..d18994be
--- /dev/null
+++ b/tests/edge_cases/stt_v1_client_example/Program.cs
@@ -0,0 +1,73 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Models.Listen.v1.WebSocket;
+using ListenV1 = Deepgram.Clients.Listen.v1.WebSocket;
+
+namespace SampleApp
+{
+    class Program
+    {
+        static async Task Main(string[] args)
+        {
+            // Initialize Library with default logging
+            Library.Initialize();
+
+            // use the client factory with a API Key set with the "DEEPGRAM_API_KEY" environment variable
+            var liveClient = ClientFactory.CreateListenWebSocketClient(1) as ListenV1.Client;
+            if (liveClient == null)
+            {
+                Console.WriteLine("Failed to create ListenWebSocketClient");
+                return;
+            }
+
+            // Subscribe to the EventResponseReceived event
+            liveClient.Subscribe(new EventHandler<ResultResponse>((sender, e) =>
+            {
+                if (e.Channel.Alternatives[0].Transcript == "")
+                {
+                    return;
+                }
+                Console.WriteLine($"Speaker: {e.Channel.Alternatives[0].Transcript}");
+            }));
+
+            // Start the connection
+            var liveSchema = new LiveSchema()
+            {
+                Model = "nova-2",
+                Punctuate = true,
+                SmartFormat = true,
+            };
+            await liveClient.Connect(liveSchema);
+
+            // get the webcast data... this is a blocking operation
+            try
+            {
+                var url = "http://stream.live.vc.bbcmedia.co.uk/bbc_world_service";
+                using (HttpClient client = new HttpClient())
+                {
+                    using (Stream receiveStream = await client.GetStreamAsync(url))
+                    {
+                        while (liveClient.IsConnected())
+                        {
+                            byte[] buffer = new byte[2048];
+                            await receiveStream.ReadAsync(buffer, 0, buffer.Length);
+                            liveClient.Send(buffer);
+                        }
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine(e.Message);
+            }
+
+            // Stop the connection
+            await liveClient.Stop();
+
+            // Teardown Library
+            Library.Terminate();
+        }
+    }
+}
diff --git a/tests/edge_cases/stt_v1_client_example/Streaming.csproj b/tests/edge_cases/stt_v1_client_example/Streaming.csproj
new file mode 100644
index 00000000..f02f97b4
--- /dev/null
+++ b/tests/edge_cases/stt_v1_client_example/Streaming.csproj
@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net6.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\..\Deepgram\Deepgram.csproj" />
+  </ItemGroup>
+  
+  <ItemGroup>
+    <Using Include="Deepgram" />
+  </ItemGroup>
+  
+  <ItemGroup>
+    <None Update="preamble.wav">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
+
+
+</Project>
diff --git a/tests/edge_cases/tts_v1_client_example/Program.cs b/tests/edge_cases/tts_v1_client_example/Program.cs
new file mode 100644
index 00000000..bb87e447
--- /dev/null
+++ b/tests/edge_cases/tts_v1_client_example/Program.cs
@@ -0,0 +1,172 @@
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
+// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
+// SPDX-License-Identifier: MIT
+
+using Deepgram.Models.Authenticate.v1;
+using Deepgram.Models.Speak.v1.WebSocket;
+using SpeakV1 = Deepgram.Clients.Speak.v1.WebSocket;
+using Deepgram.Logger;
+
+
+namespace SampleApp
+{
+    class Program
+    {
+        static async Task Main(string[] args)
+        {
+            // Initialize Library with default logging
+            // Normal logging is "Info" level
+            //Library.Initialize();
+            // OR very chatty logging
+            Library.Initialize(LogLevel.Verbose); // LogLevel.Default, LogLevel.Debug, LogLevel.Verbose
+
+            //// use the client factory with a API Key set with the "DEEPGRAM_API_KEY" environment variable
+            //DeepgramWsClientOptions options = new DeepgramWsClientOptions();
+            //options.AutoFlushSpeakDelta = 1000;
+            //var speakClient = ClientFactory.CreateSpeakWebSocketClient("", options);
+            var speakClient = ClientFactory.CreateSpeakWebSocketClient(1) as SpeakV1.Client;
+            if (speakClient == null)
+            {
+                Console.WriteLine("Failed to create SpeakWebSocketClient");
+                return;
+            }
+
+            // append wav header only once
+            bool appendWavHeader = true;
+
+            // Subscribe to the EventResponseReceived event
+            speakClient.Subscribe(new EventHandler<OpenResponse>((sender, e) =>
+            {
+                Console.WriteLine($"\n\n----> {e.Type} received");
+            }));
+            speakClient.Subscribe(new EventHandler<MetadataResponse>((sender, e) =>
+            {
+                Console.WriteLine($"----> {e.Type} received");
+                Console.WriteLine($"----> RequestId: {e.RequestId}");
+            }));
+            speakClient.Subscribe(new EventHandler<AudioResponse>((sender, e) =>
+            {
+                Console.WriteLine($"----> {e.Type} received");
+
+                // add a wav header
+                if (appendWavHeader)
+                {
+                    using (BinaryWriter writer = new BinaryWriter(File.Open("output.wav", FileMode.Append)))
+                    {
+                        Console.WriteLine("Adding WAV header to output.wav");
+                        byte[] wavHeader = new byte[44];
+                        int sampleRate = 48000;
+                        short bitsPerSample = 16;
+                        short channels = 1;
+                        int byteRate = sampleRate * channels * (bitsPerSample / 8);
+                        short blockAlign = (short)(channels * (bitsPerSample / 8));
+
+                        wavHeader[0] = 0x52; // R
+                        wavHeader[1] = 0x49; // I
+                        wavHeader[2] = 0x46; // F
+                        wavHeader[3] = 0x46; // F
+                        wavHeader[4] = 0x00; // Placeholder for file size (will be updated later)
+                        wavHeader[5] = 0x00; // Placeholder for file size (will be updated later)
+                        wavHeader[6] = 0x00; // Placeholder for file size (will be updated later)
+                        wavHeader[7] = 0x00; // Placeholder for file size (will be updated later)
+                        wavHeader[8] = 0x57; // W
+                        wavHeader[9] = 0x41; // A
+                        wavHeader[10] = 0x56; // V
+                        wavHeader[11] = 0x45; // E
+                        wavHeader[12] = 0x66; // f
+                        wavHeader[13] = 0x6D; // m
+                        wavHeader[14] = 0x74; // t
+                        wavHeader[15] = 0x20; // Space
+                        wavHeader[16] = 0x10; // Subchunk1Size (16 for PCM)
+                        wavHeader[17] = 0x00; // Subchunk1Size
+                        wavHeader[18] = 0x00; // Subchunk1Size
+                        wavHeader[19] = 0x00; // Subchunk1Size
+                        wavHeader[20] = 0x01; // AudioFormat (1 for PCM)
+                        wavHeader[21] = 0x00; // AudioFormat
+                        wavHeader[22] = (byte)channels; // NumChannels
+                        wavHeader[23] = 0x00; // NumChannels
+                        wavHeader[24] = (byte)(sampleRate & 0xFF); // SampleRate
+                        wavHeader[25] = (byte)((sampleRate >> 8) & 0xFF); // SampleRate
+                        wavHeader[26] = (byte)((sampleRate >> 16) & 0xFF); // SampleRate
+                        wavHeader[27] = (byte)((sampleRate >> 24) & 0xFF); // SampleRate
+                        wavHeader[28] = (byte)(byteRate & 0xFF); // ByteRate
+                        wavHeader[29] = (byte)((byteRate >> 8) & 0xFF); // ByteRate
+                        wavHeader[30] = (byte)((byteRate >> 16) & 0xFF); // ByteRate
+                        wavHeader[31] = (byte)((byteRate >> 24) & 0xFF); // ByteRate
+                        wavHeader[32] = (byte)blockAlign; // BlockAlign
+                        wavHeader[33] = 0x00; // BlockAlign
+                        wavHeader[34] = (byte)bitsPerSample; // BitsPerSample
+                        wavHeader[35] = 0x00; // BitsPerSample
+                        wavHeader[36] = 0x64; // d
+                        wavHeader[37] = 0x61; // a
+                        wavHeader[38] = 0x74; // t
+                        wavHeader[39] = 0x61; // a
+                        wavHeader[40] = 0x00; // Placeholder for data chunk size (will be updated later)
+                        wavHeader[41] = 0x00; // Placeholder for data chunk size (will be updated later)
+                        wavHeader[42] = 0x00; // Placeholder for data chunk size (will be updated later)
+                        wavHeader[43] = 0x00; // Placeholder for data chunk size (will be updated later)
+
+                        writer.Write(wavHeader);
+                        appendWavHeader = false;
+                    }
+                }
+
+                if (e.Stream != null)
+                {
+                    using (BinaryWriter writer = new BinaryWriter(File.Open("output.wav", FileMode.Append)))
+                    {
+                        writer.Write(e.Stream.ToArray());
+                    }
+                }
+            }));
+            speakClient.Subscribe(new EventHandler<FlushedResponse>((sender, e) =>
+            {
+                Console.WriteLine($"----> {e.Type} received");
+            }));
+            speakClient.Subscribe(new EventHandler<ClearedResponse>((sender, e) =>
+            {
+                Console.WriteLine($"----> {e.Type} received");
+            }));
+            speakClient.Subscribe(new EventHandler<CloseResponse>((sender, e) =>
+            {
+                Console.WriteLine($"----> {e.Type} received");
+            }));
+            speakClient.Subscribe(new EventHandler<UnhandledResponse>((sender, e) =>
+            {
+                Console.WriteLine($"----> {e.Type} received");
+            }));
+            speakClient.Subscribe(new EventHandler<WarningResponse>((sender, e) =>
+            {
+                Console.WriteLine($"----> {e.Type} received");
+            }));
+            speakClient.Subscribe(new EventHandler<ErrorResponse>((sender, e) =>
+            {
+                Console.WriteLine($"----> {e.Type} received. Error: {e.Message}");
+            }));
+
+            // Start the connection
+            var speakSchema = new SpeakSchema()
+            {
+                Encoding = "linear16",
+                SampleRate = 48000,
+            };
+            await speakClient.Connect(speakSchema);
+
+            // Send some Text to convert to audio
+            speakClient.SpeakWithText("Hello World!");
+
+            //Flush the audio
+            speakClient.Flush();
+
+            // Wait for the user to press a key
+            Console.WriteLine("\n\nPress any key to stop and exit...\n\n\n");
+            Console.ReadKey();
+
+            // Stop the connection
+            await speakClient.Stop();
+
+            // Terminate Libraries
+            Library.Terminate();
+        }
+    }
+}
diff --git a/tests/edge_cases/tts_v1_client_example/Speak.csproj b/tests/edge_cases/tts_v1_client_example/Speak.csproj
new file mode 100644
index 00000000..33d8f4ee
--- /dev/null
+++ b/tests/edge_cases/tts_v1_client_example/Speak.csproj
@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+	<PropertyGroup>
+		<OutputType>Exe</OutputType>
+		<TargetFramework>net6.0</TargetFramework>
+		<ImplicitUsings>enable</ImplicitUsings>
+		<Nullable>enable</Nullable>	
+	</PropertyGroup>
+	
+	<ItemGroup>	
+		<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
+	</ItemGroup>
+	
+	<ItemGroup>
+	  <ProjectReference Include="..\..\..\Deepgram\Deepgram.csproj" />
+	</ItemGroup>
+
+    <ItemGroup>
+        <Using Include="Deepgram" />
+    </ItemGroup>
+
+</Project>
diff --git a/tests/expected_failures/websocket/exercise_timeout/Program.cs b/tests/expected_failures/websocket/exercise_timeout/Program.cs
index 763aa062..859e4290 100644
--- a/tests/expected_failures/websocket/exercise_timeout/Program.cs
+++ b/tests/expected_failures/websocket/exercise_timeout/Program.cs
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT license that can be found in the LICENSE file.
 // SPDX-License-Identifier: MIT
 
-using Deepgram.Models.Listen.v1.WebSocket;
+using Deepgram.Models.Listen.v2.WebSocket;
 using Deepgram.Logger;
 
 namespace SampleApp
@@ -20,11 +20,11 @@ static async Task Main(string[] args)
             var liveClient = ClientFactory.CreateListenWebSocketClient();
 
             // Subscribe to the EventResponseReceived event
-            liveClient.Subscribe(new EventHandler<OpenResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<OpenResponse>((sender, e) =>
             {
                 Console.WriteLine($"----> {e.Type} received");
             }));
-            liveClient.Subscribe(new EventHandler<ResultResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<ResultResponse>((sender, e) =>
             {
                 if (e.Channel.Alternatives[0].Transcript == "")
                 {
@@ -34,11 +34,11 @@ static async Task Main(string[] args)
                 // Console.WriteLine("Transcription received: " + JsonSerializer.Serialize(e.Transcription));
                 Console.WriteLine($"\n\n\n----> Speaker: {e.Channel.Alternatives[0].Transcript}\n\n\n");
             }));
-            liveClient.Subscribe(new EventHandler<CloseResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<CloseResponse>((sender, e) =>
             {
                 Console.WriteLine($"----> {e.Type} received");
             }));
-            liveClient.Subscribe(new EventHandler<ErrorResponse>((sender, e) =>
+            await liveClient.Subscribe(new EventHandler<ErrorResponse>((sender, e) =>
             {
                 Console.WriteLine($"----> {e.Type} received. Error: {e.Message}");
             }));