MyWebApi.Sdk 0.3.0

dotnet add package MyWebApi.Sdk --version 0.3.0
                    
NuGet\Install-Package MyWebApi.Sdk -Version 0.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MyWebApi.Sdk" Version="0.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MyWebApi.Sdk" Version="0.3.0" />
                    
Directory.Packages.props
<PackageReference Include="MyWebApi.Sdk" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add MyWebApi.Sdk --version 0.3.0
                    
#r "nuget: MyWebApi.Sdk, 0.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package MyWebApi.Sdk@0.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=MyWebApi.Sdk&version=0.3.0
                    
Install as a Cake Addin
#tool nuget:?package=MyWebApi.Sdk&version=0.3.0
                    
Install as a Cake Tool

MyWebApi.Sdk — .NET SDK for the CPlugin WebAPI

.NET client for the MyWebAPI.com trading platform management API (v2). Usable from any .NET language (C#/F#/VB); targets netstandard2.0 + net8.0.

Two NuGet packages, one version and release cycle (root namespaces in code are CPlugin.SaaSWebApi.*):

  • MyWebApi.Sdk — the full SDK: CPluginWebApiClient with a generated method for every v2 endpoint across both supported platform families, OAuth2 client_credentials with transparent refresh, safe-method-only 401 replay, typed ApiError, cursor pagination, SignalR real-time clients with auto-reconnect, optional DI integration.
  • MyWebApi.Sdk.Models — generated POCO DTOs + v2 response envelopes only. Zero dependencies beyond System.Text.Json. Use this when you build your own HTTP layer.

Install

dotnet add package MyWebApi.Sdk

Environment presets

Pick an environment at construction time — no URL configuration needed:

Environment API base URL Auth URL
CPluginEnvironment.Prod https://cloud.mywebapi.com https://auth.cplugin.net
CPluginEnvironment.Staging https://pre.mywebapi.com https://pre.auth.cplugin.net
CPluginEnvironment.Custom supply ApiBaseUrl + Authority —

Client credentials (ID and secret) are managed through the CPlugin Toolbox:

Quick start

using CPlugin.SaaSWebApi.Client;

using var client = new CPluginWebApiClient(CPluginEnvironment.Staging, clientId, clientSecret);

// * Discover platforms available to this credential.
var platforms = await client.ListTradePlatformsAsync();
var tradePlatform = Guid.Parse(platforms[0]!["id"]!.GetValue<string>());

// * Bind a platform once; every v2 endpoint is a method on the namespace.
var mt4 = client.MT4(tradePlatform);
var time = await mt4.ServerTimeAsync();          // token acquisition + refresh under the hood
var user = await mt4.UserRecordGetAsync(1001);   // typed DTOs with XML-doc from the API spec

Token management (OAuth2 client_credentials flow) is fully automatic: lazy acquisition on first call, bounded discovery caching with invalidation after failures, expiry skew, single-flight refresh, and one 401 retry only for GET/HEAD/OPTIONS. Unsafe requests are never replayed after a 401.

using CPlugin.SaaSWebApi.Client;
using CPlugin.SaaSWebApi.Client.DependencyInjection;

services.AddCPluginWebApiSdk(sp => new()
{
    Environment  = CPluginEnvironment.Prod,
    ClientId     = "your-client-id",
    ClientSecret = builder.Configuration["CPlugin:ClientSecret"],
});

// In a service:
public sealed class MyService(CPluginWebApiClient client)
{
    public Task<DateTimeOffset> Probe(Guid tp) => client.MT4(tp).ServerTimeAsync();
}

The DI extension wires IHttpClientFactory-backed HttpClients, the OAuth2 handler chain, and a bounded resilience pipeline that retries only GET/HEAD/OPTIONS on transient HTTP status responses; unsafe methods are never repeated automatically (see Timeouts and retries). Options are validated on first resolution and surface as OptionsValidationException.

Static token (advanced / testing)

using var client = new CPluginWebApiClient(new CPluginWebApiClientOptions
{
    Environment = CPluginEnvironment.Staging,
    Token = pastedJwt, // no refresh-on-expiry in this mode
});

Pagination

Cursor-paginated endpoints return Page<T> (Items, NextCursor, HasMore). PageIterator walks the cursor for you:

var mt4 = client.MT4(tradePlatform);

// * Page by page — no full dataset loaded into memory at once.
await foreach (var page in PageIterator.PagesAsync(cur => mt4.UsersRequestAsync(limit: 100, cursor: cur)))
    foreach (var user in page.Items)
        Console.WriteLine($"{user.Login} {user.Balance}");

// * Or as a flat item stream.
await foreach (var trade in PageIterator.ItemsAsync(cur => mt4.TradesRequestAsync(limit: 200, cursor: cur)))
    Process(trade);

Error handling

Methods return the payload directly. When the v2 envelope carries an error, the SDK throws ApiError:

try
{
    var user = await mt4.UserRecordGetAsync(login);
}
catch (ApiError err)
{
    Console.WriteLine($"[{err.Code}] {err.Description}");
    // * Quote ActivityId when contacting support — it locates the request in server logs.
    Console.WriteLine($"activity: {err.ActivityId}, manager code: {err.ManagerCode}");
}

OAuth2 token-endpoint and OIDC discovery failures throw CPlugin.SaaSWebApi.Client.Auth.OAuth2TokenException (a subclass of HttpRequestException). Catch HttpRequestException broadly to handle auth and transport failures uniformly.

Timeouts and retries

Almost every call addressed to a trading platform has a server-side deadline. When the trading server does not answer in time, the API answers with an error instead of waiting indefinitely. Defaults per kind of operation (each method's XML documentation names its own):

Operation kind Default
trade 5 s
read 10 s
change 15 s
history 30 s
maintenance 60 s

Choose another deadline, from 1 to 300 seconds, per call or for the whole client. The SDK sends it as the X-Request-Timeout header:

// * One call.
var trades = await mt4.ReportsRequestAsync(from, to, options: new CallOptions { RequestTimeout = TimeSpan.FromSeconds(90) });

// * Every call that sets none of its own.
using var client = new CPluginWebApiClient(new CPluginWebApiClientOptions
{
    Environment    = CPluginEnvironment.Prod,
    ClientId       = clientId,
    ClientSecret   = clientSecret,
    RequestTimeout = TimeSpan.FromSeconds(20),
});

The client waits for the answer 30 s longer than the server-side deadline (the server may extend a deadline by up to 20 s while it connects to the platform), so that normally the server's own answer arrives first; CPluginWebApiClientOptions.Timeout (default 30 s) is only the minimum wait. Every operation has a server-side deadline and honours X-Request-Timeout, including those served by the x86 sidecar (plugins, mail, news, snapshots, sync, binary external commands). If the client-side wait passes — a slow network, a slow token request — the call throws TaskCanceledException wrapping a TimeoutException. For a change or a trade that means the outcome is unknown: treat it exactly like OutcomeUnknown below.

A request that did not finish in time throws ApiError with one of these codes; Outcome carries the X-Request-Outcome response header:

Code Outcome Meaning What to do
Timeout timeout A read did not finish. Nothing was changed. Repeat, possibly with a longer RequestTimeout.
Busy not-started Refused before it was sent to the trading platform. Repeat after a pause.
OutcomeUnknown unknown A change or a trade did not finish and may still be applied. Do not repeat blindly — see below.
OutcomeUnknown in-progress A request with the same Idempotency-Key is still running; this one was not executed. Repeat later with the same key.

ApiError.IsSafeToRetry is true only for the first two rows; IsTimeout, IsBusy, IsOutcomeUnknown and IsInProgress name each case, and ApiErrorCodes / RequestOutcomes hold the values.

Recovery after OutcomeUnknown: send every change and trade with an Idempotency-Key, and repeat it with the same key. The server executes a key once: while the first request still runs, a repeat gets OutcomeUnknown with Outcome = in-progress and is not executed; once it has finished, a repeat gets the original result.

var key = Guid.NewGuid().ToString();
for (var attempt = 1; ; attempt++)
{
    try
    {
        return await mt4.TradeTransactionAsync(trade, new CallOptions { IdempotencyKey = key });
    }
    catch (ApiError err) when (err.IsOutcomeUnknown && attempt < 5)
    {
        await Task.Delay(TimeSpan.FromSeconds(2 * attempt)); // same key: executed at most once
    }
}

Without a key, check the resulting state (the order, the balance, the record) before deciding to repeat.

The SDK never repeats a change or a trade on its own: not on OutcomeUnknown, not on a lost connection, not on a transient HTTP status, and not after a 401. The DI pipeline retries only GET/HEAD/OPTIONS on transient HTTP statuses and transport faults, and never a response whose outcome is unknown or in-progress. Timeout and Busy are not retried automatically either — the decision stays with the caller.

SignalR hub method calls addressed to a platform, and the v2 hub connection itself, fail after 60 s on the server side; subscriptions and streams are not affected.

Real-time / SignalR

Both v2 hubs are first-class (Microsoft.AspNetCore.SignalR.Client, auto-reconnect). The Realtime accessor shares the REST client's cached token — no second OAuth round-trip:

await using var hub = client.Realtime.MT4(tradePlatform); // or client.Realtime.MT5(...)

// ! Attach handlers BEFORE StartAsync — the server pushes the first
// ! OnConnectionStatus right after the handshake.
hub.OnConnectionStatus(s => Console.WriteLine($"connected: {s.Connected}"));

await hub.StartAsync();
await hub.SubscribeToTicksAsync("EURUSD");

await foreach (var tick in hub.StreamTicksAsync("EURUSD", ct))
    Console.WriteLine($"{tick.Symbol} {tick.Bid}/{tick.Ask}");

The client.Realtime.MT4(...) hub streams ticks, trades, margin-call events, user updates, and symbol config changes; the client.Realtime.MT5(...) hub streams connection status and margin-call updates (additional streams are deferred server-side).

Examples

Runnable projects under examples/ (staging, credentials via WEBAPI_CLIENT_ID / WEBAPI_CLIENT_SECRET env vars):

dotnet run --project examples/QuickStart   # auth, platform discovery, server time, paging
dotnet run --project examples/Streaming    # live tick stream over SignalR, Ctrl+C to stop

Regenerate

The repository pins the exact .NET SDK used by regeneration and tests in global.json; run these commands from the repository root.

The whole endpoint surface is generated from the committed spec snapshot spec/v2.json:

./scripts/fetch-spec.sh          # refresh spec/v2.json (WEBAPI_BASE_URL to pick the host)
./scripts/generate-models.sh     # NSwag → src/CPlugin.SaaSWebApi.Models/Generated/Dto.g.cs
./scripts/generate-endpoints.sh  # bespoke generator → src/.../Generated/MT4Endpoints.g.cs + MT5Endpoints.g.cs

generate-models.sh requires dotnet tool install --global NSwag.ConsoleCore. generate-endpoints.sh uses the in-repo tool under scripts/GenerateEndpoints/ and needs no extra tools. Generated files (*.g.cs) are committed and machine-owned — never edit them by hand.

Test

dotnet test tests/CPlugin.SaaSWebApi.Client.Tests/CPlugin.SaaSWebApi.Client.Tests.csproj

# Gated staging E2E (REST only):
WEBAPI_E2E=1 WEBAPI_CLIENT_ID=... WEBAPI_CLIENT_SECRET=... WEBAPI_TRADE_PLATFORM=... \
  dotnet test --filter StagingE2ETests

Layout

.
├── CPlugin.SaaSWebApi.Client.sln
├── src/
│   ├── CPlugin.SaaSWebApi.Models/
│   │   └── Generated/Dto.g.cs            # NSwag output: DTOs + v2 envelopes (machine-owned)
│   └── CPlugin.SaaSWebApi.Client/
│       ├── Generated/                    # MT4Endpoints.g.cs / MT5Endpoints.g.cs (machine-owned)
│       ├── Auth/                         # TokenCache, OidcDiscoveryClient, ClientCredentialsHandler
│       ├── CPluginWebApiClient.cs        # entry point: MT4()/MT5()/Realtime/ListTradePlatformsAsync
│       ├── Environments.cs               # env presets (prod / staging / custom)
│       ├── ApiError.cs                   # envelope error exception {Code, Description, ActivityId, Outcome}
│       ├── CallOptions.cs                # per-call idempotency key, fields, request timeout, cancellation
│       ├── Page.cs                       # Page<T> + PageIterator cursor helpers
│       ├── MT4V2SignalRClient.cs         # /hubs/mt4/v2
│       ├── MT5V2SignalRClient.cs         # /hubs/mt5/v2
│       └── DependencyInjection/          # AddCPluginWebApiSdk (net8.0 only)
├── tests/CPlugin.SaaSWebApi.Client.Tests/  # hermetic contract tests + gated E2E/
├── examples/                             # QuickStart, Streaming
├── spec/v2.json                          # OpenAPI spec snapshot (source of the generated surface)
└── scripts/                              # fetch-spec, generate-models (NSwag), GenerateEndpoints (bespoke facade)

License

MIT. Publishing to NuGet.org is a separate, explicit release step — see PUBLISHING.md.

Trademarks

MetaTrader, MT4, MT5, and MetaQuotes are trademarks or registered trademarks of MetaQuotes Ltd. This project is an independent SDK for the WebAPI service. It is not affiliated with, endorsed by, or sponsored by MetaQuotes Ltd.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.0 69 9/30/2026
0.2.1 96 9/17/2026
0.1.0 132 7/4/2026

0.3.0: request timeouts — CallOptions.RequestTimeout and CPluginWebApiClientOptions.RequestTimeout (X-Request-Timeout, 1–300 s); the client waits 30 s past the server-side deadline; new error codes Timeout, OutcomeUnknown, Busy with ApiError.Outcome, IsSafeToRetry and related helpers; writes are never retried automatically; PATCH methods now take the patch object. Breaking: flag-set properties and parameters are now strings ("A, B"), WebApiErrorCode gains members (Internal changes its numeric value). Full notes: https://github.com/CPlugin/mywebapi.com-sdk-dotnet/blob/v0.3.0/CHANGELOG.md