MonoCloud Management .NET SDK (MonoCloud.Management)
Typed .NET SDK for the MonoCloud Management API. Use it to programmatically manage users, applications, groups, API resources, sign-in options, branding, logs, signing keys, network zones, and PKI/SPIFFE trust stores from .NET (Framework 4.6.2+, .NET Standard 2.0, or modern .NET).
Package identity — read this first
Use: the MonoCloud.Management NuGet package. Check *.csproj / packages.config before writing code — confirm the <PackageReference Include="MonoCloud.Management" ... /> is present and note its version.
This is not the same as:
MonoCloud.AspNetCore.Authentication/ any auth middleware (user-facing OIDC sign-in — outside this skill).MonoCloud.Management.Core— the internal core package thatMonoCloud.Managementdepends on. Do not add a project/package reference to it directly; app code depends only onMonoCloud.Management.
Stale-training-data guards — none of these exist in this SDK; do not emit them:
- There is no
MonoCloudManagementApi,ManagementApiClient, orApiCliententry type. The entry type isMonoCloudManagementClient. - Response bodies are read from
.Data, not.Result,.Body, or.Value. Status is.Status, not.StatusCode. - The SDK reads no environment variables of its own (no
MONOCLOUD_MANAGEMENT_*fallback). Config flows throughIConfiguration/ options only.
Installation
dotnet add package MonoCloud.Management
Install-Package MonoCloud.Management
Supported targets: .NET Framework 4.6.2+, .NET Standard 2.0, and any modern .NET 6.0+ that consumes netstandard2.0.
Authentication — Management API key
A Management API key (generated in the MonoCloud dashboard → Settings → API Keys) is required. It is tenant-scoped with full admin permissions — treat it like a root credential:
- Never check it into source control.
- Read it from
IConfiguration(appsettings.json+ environment variables / User Secrets / Key Vault / etc.).
The SDK authenticates by sending the key in the X-API-KEY request header. It sets BaseAddress to {Domain}/api/ under the hood.
Environment variables and configuration keys
The .NET SDK (v0.2.11) reads configuration from the MonoCloud:Management section of IConfiguration — it does not read process environment variables on its own. You can still surface env vars through the standard ASP.NET Core configuration mapping (double-underscore).
| Config key | Env-var form (ASP.NET Core) | Required | Purpose |
|---|---|---|---|
MonoCloud:Management:Domain |
MonoCloud__Management__Domain |
yes | Tenant URL, e.g. https://acme.us.monocloud.com |
MonoCloud:Management:ApiKey |
MonoCloud__Management__ApiKey |
yes | Management API key |
MonoCloud:Management:Timeout |
MonoCloud__Management__Timeout |
no | Request timeout in seconds (default 10) |
Domain is sanitized on construction: a missing https:// prefix is added and a trailing / is stripped. Store the API key in User Secrets locally (dotnet user-secrets set "MonoCloud:Management:ApiKey" "...") and a secret manager in production — never in a shipped appsettings.json.
Quick start — DI (recommended)
appsettings.Development.json (API key comes from User Secrets, not this file):
{
"MonoCloud": {
"Management": {
"Domain": "https://your-tenant.us.monocloud.com",
"Timeout": "30"
}
}
}
Program.cs:
using MonoCloud.Management;
var builder = WebApplication.CreateBuilder(args);
// Reads the MonoCloud:Management section from IConfiguration.
builder.Services.AddMonoCloudManagementClient(builder.Configuration);
var app = builder.Build();
app.MapGet("/users", async (MonoCloudManagementClient management) =>
{
var response = await management.Users.GetAllUsersAsync(page: 1, size: 25);
return Results.Ok(response.Data); // response.Data is List<UserSummary>
});
app.Run();
Inject MonoCloudManagementClient wherever you need it. AddMonoCloudManagementClient registers it as transient, backed by a named IHttpClientFactory client ("MonoCloudManagementClient"), so pooling/retries/policies layer cleanly on top.
Mixing DI options with code
AddMonoCloudManagementClient also accepts an Action<MonoCloudManagementOptions> — alone, or alongside IConfiguration. When both are supplied, the options action wins for any value it sets. Registration throws ArgumentNullException if Domain or ApiKey is empty after merging.
builder.Services.AddMonoCloudManagementClient(builder.Configuration, options =>
{
options.ApiKey = builder.Configuration["Secrets:MonoCloudApiKey"];
options.Timeout = TimeSpan.FromSeconds(60);
});
MonoCloudManagementOptions is { string? Domain; string? ApiKey; TimeSpan? Timeout; }.
Quick start — direct construction
For console apps, background workers, or scenarios without DI:
using MonoCloud.Management;
using MonoCloud.Management.Core.Base; // MonoCloudConfig
var config = new MonoCloudConfig(
domain: "https://your-tenant.us.monocloud.com",
apiKey: "your-management-api-key",
timeout: TimeSpan.FromSeconds(30) // optional; defaults to 10s
);
var management = new MonoCloudManagementClient(config);
var response = await management.Users.GetAllUsersAsync(1, 25);
MonoCloudConfig just carries Domain, ApiKey, and Timeout — it does not validate on its own. The non-empty Domain/ApiKey check runs when the client is built (in MonoCloudClientBase), so a bad config throws MonoCloudException (Tenant Domain is required / API Key is required) at new MonoCloudManagementClient(config), not at new MonoCloudConfig(...).
MonoCloudManagementClient also accepts a pre-built HttpClient — useful for integration tests and custom HTTP pipelines (see DI registration and HTTP-layer replacement):
var http = new HttpClient { BaseAddress = new Uri("https://example.com/api/") };
http.DefaultRequestHeaders.Add("X-API-KEY", "test-key");
var management = new MonoCloudManagementClient(http);
Client surface
MonoCloudManagementClient exposes 10 resource-client properties:
| Property | Resource area | Backing type |
|---|---|---|
.Users |
Users: CRUD, identifiers, passwords, passkeys, claims, sessions, grants | UsersClient |
.Clients |
OAuth/OIDC applications (operates on the Application model) |
ClientsClient |
.Groups |
Groups: CRUD | GroupsClient |
.Resources |
API resources, API scopes, API access policies, scopes, claim resources | ResourcesClient |
.Keys |
Signing key material: list, rotate, revoke | KeysClient |
.Logs |
Audit / event logs: list, find | LogsClient |
.NetworkZones |
IP + regional network zones (ScaleX for create/patch) | NetworkZonesClient |
.Options |
Tenant options: authentication, communication, sign-up custom fields, external identity providers | OptionsClient |
.Branding |
Branding options for pages, emails, SMS | BrandingClient |
.TrustStores |
PKI (mTLS) + SPIFFE trust stores, revocations, bans | TrustStoresClient |
All resource-client classes live in namespace MonoCloud.Management.Clients and derive from MonoCloudClientBase. Every method is PascalCase, ends in Async, and takes a trailing CancellationToken cancellationToken = default. See references/api-surface.md for the full, per-method index (signatures verbatim, including subscription-tier notes).
Naming quirk —
Clientsoperates onApplication. The.Clientsaccessor /ClientsClientmanages theApplicationresource:GetAllApplicationsAsync,CreateApplicationAsync,PatchApplicationRequest, etc. Path params are namedclientId(astring), but there is noClientmodel — do not expect one.
Response shape
Every method returns one of three envelopes from namespace MonoCloud.Management.Core.Base:
public class MonoCloudResponse
{
public int Status { get; } // HTTP status
public IDictionary<string, IEnumerable<string>> Headers { get; } // multi-valued
}
public class MonoCloudResponse<T> : MonoCloudResponse
{
public T Data { get; } // deserialized body
}
// Paginated list variant adds .PageData
public class MonoCloudResponse<T, TPage> : MonoCloudResponse<T> where TPage : PageModel
{
public TPage PageData { get; } // TPage is always PageModel; zero-valued if the server omits x-pagination
}
public class PageModel // namespace MonoCloud.Management.Core.Helpers
{
public int PageSize { get; set; }
public int CurrentPage { get; set; }
public int TotalCount { get; set; }
public bool HasPrevious { get; set; }
public bool HasNext { get; set; }
}
- The body property is
Data(notResult); the status property isStatus(notStatusCode). - Void-return operations (Delete / Remove / Revoke / Rotate / ban-removal /
AssignGroupToApplicationAsync) return the bareMonoCloudResponse— there is no.Data; read.Status/.Headers. - A few list endpoints return
MonoCloudResponse<List<T>>(noPageData):GetAllApplicationSecretsAsync,GetAllApiResourceSecretsAsync,GetAllSignUpCustomFieldsAsync,GetAllPkiBannedCertificatesAsync,GetAllSpiffeBannedSvidsAsync. Most other list endpoints are paginated (MonoCloudResponse<List<T>, PageModel>).
Pagination
Pagination metadata arrives in the x-pagination response header and lands in .PageData. Idiomatic drain loop:
async IAsyncEnumerable<UserSummary> EachUserAsync(
MonoCloudManagementClient management,
[EnumeratorCancellation] CancellationToken ct = default)
{
var page = 1;
while (true)
{
var response = await management.Users.GetAllUsersAsync(page, size: 100, cancellationToken: ct);
foreach (var u in response.Data) yield return u;
if (!response.PageData.HasNext) yield break;
page++;
}
}
Paginated list methods share the (int? page = 1, int? size = 10, string? filter = default, string? sort = default, CancellationToken) shape:
page— 1-indexed (defaults to 1).size— items per page (defaults to 10).filter— Lucene-style expression (per-endpoint; see the API docs).sort—"<field>:<1|-1>"(1 ascending, -1 descending).
Common operations
Create a user
var created = await management.Users.CreateUserAsync(new CreateUserRequest
{
Email = "alice@example.com",
EmailVerified = true,
Name = "Alice Example",
});
var userId = created.Data.UserId; // the identifier field is UserId (string), not Id
Look up a user, handling not-found
try
{
var response = await management.Users.FindUserByIdAsync(userId);
return response.Data; // User
}
catch (MonoCloudNotFoundException)
{
return null;
}
Patch claims / metadata (partial update)
await management.Users.PatchPrivateDataAsync(userId, new UpdatePrivateDataRequest
{
PrivateData = new Dictionary<string, object> { ["onboarded"] = true, ["plan"] = "pro" }
});
Patch*Request bodies use Optional<T> per property — only fields you explicitly assign are serialized, so PATCH is a true partial update. Resource identifiers are path-only and never appear in the patch body (they cannot be changed).
Disable a user
await management.Users.DisableUserAsync(userId, new DisableUserRequest { RevokeSessions = true });
List applications
// The accessor is .Clients, but the methods and models talk about Application*.
var apps = await management.Clients.GetAllApplicationsAsync(page: 1, size: 50);
foreach (var app in apps.Data) { /* app is Application */ }
Read audit logs
var logs = await management.Logs.GetAllLogsAsync(page: 1, size: 20, sort: "created:-1");
foreach (var log in logs.Data) { /* log is Log */ }
var management.Logs.FindLogByIdAsync(logId); // logId is a Guid
Errors
Every non-2xx response throws a typed exception. All derive from MonoCloudException (namespace MonoCloud.Management.Core.Exception); HTTP-status exceptions derive from MonoCloudRequestException, which exposes ProblemDetails? Response.
| Class | Thrown for |
|---|---|
MonoCloudBadRequestException |
400 |
MonoCloudUnauthorizedException |
401 — bad/missing X-API-KEY |
MonoCloudPaymentRequiredException |
402 — subscription/billing required |
MonoCloudForbiddenException |
403 — feature not on the current plan / insufficient tier |
MonoCloudNotFoundException |
404 |
MonoCloudConflictException |
409 |
MonoCloudIdentityValidationException |
422 (identity validation) — .Errors is IEnumerable<IdentityError> |
MonoCloudKeyValidationException |
422 (field validation) — .Errors is IDictionary<string, string[]> |
MonoCloudModelStateException |
422 (fallback / non-problem+json body) |
MonoCloudResourceExhaustedException |
429 — rate limited |
MonoCloudServerException |
5xx |
MonoCloudRequestException |
base for all HTTP-status exceptions — exposes .Response (ProblemDetails?) |
MonoCloudException |
base (Exception) — also thrown for config/transport/deserialization failures |
MonoCloudException has no StatusCode property. Branch on the subclass, or read (ex as MonoCloudRequestException)?.Response?.Status for the problem-details status.
try
{
await management.Users.CreateUserAsync(req);
}
catch (MonoCloudConflictException)
{
return Results.Conflict();
}
catch (MonoCloudIdentityValidationException ex)
{
return Results.UnprocessableEntity(ex.Errors); // IEnumerable<IdentityError>
}
catch (MonoCloudRequestException ex)
{
logger.LogError(ex, "MonoCloud Management API call failed: {Status} {Title}",
ex.Response?.Status, ex.Response?.Title);
throw;
}
Subscription tiers
Some endpoints require a paid tier; the server returns 403 (MonoCloudForbiddenException) or 402 (MonoCloudPaymentRequiredException) when the tier is insufficient.
| Tier | Gated operations |
|---|---|
| Pro | Groups.CreateGroupAsync beyond two groups; Users session methods (GetAllUserSessionsAsync, FindUserSessionAsync, RevokeUserSessionAsync); Users.GetAllUserClientGrantsAsync |
| Secure+ | Users consent/token reads (GetAllUserConsentsAsync, GetAllReferenceTokensAsync, GetAllRefreshTokensAsync, GetAllAuthorizationCodesAsync) and the matching Revoke* methods; the EnableConsent field on Application requests |
| ScaleX | NetworkZones create/patch (CreateIpNetworkZoneAsync, PatchIpNetworkZoneAsync, CreateRegionalNetworkZoneAsync, PatchRegionalNetworkZoneAsync); Clients.AssignGroupToApplicationAsync / RemoveGroupFromApplicationAsync; Resources.CreateApiResourceSecretAsync |
Several request fields (e.g. EnableConsent, PAR/JAR, back-channel logout, extended refresh-token lifetimes) also carry tier gates even on otherwise-free endpoints. The <note>…subscription…</note> XML comments in the SDK source are the source of truth — see references/api-surface.md.
DI registration and HTTP-layer replacement
AddMonoCloudManagementClient (static class MonoCloudManagementServiceExtensions, namespace MonoCloud.Management) has three overloads, all returning IServiceCollection:
AddMonoCloudManagementClient(IConfiguration configuration)— reads theMonoCloud:Managementsection.AddMonoCloudManagementClient(Action<MonoCloudManagementOptions> options)— configure in code.AddMonoCloudManagementClient(IConfiguration? configuration, Action<MonoCloudManagementOptions>? options)— both; options override configuration.
It registers a named HttpClient ("MonoCloudManagementClient") with BaseAddress = {Domain}/api/, Timeout = config.Timeout, and the X-API-KEY default header, then registers MonoCloudManagementClient as transient over IHttpClientFactory.
Bring-your-own HttpClient. The MonoCloudManagementClient(HttpClient httpClient) constructor bypasses MonoCloudConfig, so you own the full pipeline (custom HttpMessageHandler, Polly policies, proxies, mTLS, test doubles). You must set BaseAddress (ending in /api/) and the X-API-KEY header yourself.
Common pitfalls
- Hardcoding the API key in
appsettings.json. Use User Secrets for dev and a secret manager (Azure Key Vault, AWS Secrets Manager) in production. - Expecting env-var fallback. Unlike the JS SDK, this SDK reads no
MONOCLOUD_MANAGEMENT_*env vars itself. Feed values throughIConfiguration(which can bind env vars via theMonoCloud__Management__*mapping) or the options action. - Trailing
/apionDomain. Pass the bare tenant URL — the SDK appends/api/. - Milliseconds vs seconds for timeout.
MonoCloud:Management:Timeoutis seconds (mapped toTimeSpan.FromSeconds/TotalSeconds). The default is10— long-running admin calls may need it raised. - Sending immutable identifiers on
Patch…requests. Identifier fields were removed from PATCH request models in 0.2.6 (AudiencefromPatchApiResourceRequest;NamefromPatchApiScopeRequest,PatchScopeRequest,PatchClaimResourceRequest). The C# property is gone — old code that set it won't compile. - Parameter-order gotchas in
ResourcesClient.FindApiResourceSecretByIdAsync(secretId, apiId, …)and the API-scope find/patch/delete methods take(scopeId, apiId, …)— the scope/secret id comes beforeapiId. ButDeleteApiResourceSecretAsync(apiId, secretId, …)and the create methods takeapiIdfirst. Copy the signatures verbatim fromreferences/api-surface.md. Guidvsstringids. Group ids and user identifier ids areGuid(groupId,identifierId,logId); user / application / resource / zone / trust-store / session ids arestring.- Reading
response.Result/ex.StatusCode. The body is.Data, the status is.Status;MonoCloudExceptionhas noStatusCode— branch on the subclass or read(ex as MonoCloudRequestException)?.Response?.Status. new-ingMonoCloudManagementClientper request under DI. It's already registered transient overIHttpClientFactory— inject it, don't construct it in controllers.- Referencing
MonoCloud.Management.Coredirectly. The core types (MonoCloudConfig,MonoCloudResponse<T>, exceptions) come transitively withMonoCloud.Management; don't add a separate package reference. - Using ScaleX/Secure+ features without the tier.
NetworkZonescreate/patch, application↔group assignment, API-resource-secret creation, and consent/token endpoints throwMonoCloudForbiddenException(or 402) on lower plans. Verify the tenant's plan before wiring them into production.
Onboarding checklist
dotnet add package MonoCloud.Management.- Create a Management API key in the MonoCloud dashboard.
- Set
MonoCloud:Management:Domain(inappsettings.json/config) andMonoCloud:Management:ApiKey(in User Secrets / Key Vault / env var). Program.cs:builder.Services.AddMonoCloudManagementClient(builder.Configuration).- Inject
MonoCloudManagementClientand call resource clients (management.Users.GetAllUsersAsync(...), etc.). - Read results from
response.Data(andresponse.PageDatafor paginated lists). - Wrap calls in
try/catchagainst the specificMonoCloudExceptionsubclass(es) you handle. - Run
node scripts/verify.js /path/to/projectto confirm package installation + config.
Deeper reference
references/api-surface.md— every resource client and method, with verbatim signatures, response types, and subscription-tier notes.references/troubleshooting.md— symptom → cause → fix for the common failure modes (401/403, missingDomain/ApiKeyat startup, secret leaks, hand-newed clients vs DI, genericcatch (Exception), single-page reads).