// wiki / sdk

SDK libraries

Official client libraries you install from your package manager. They wrap the same endpoints documented on the HTTP & REST page and authenticate with the same Management Key — you just stop writing the HTTP by hand.

Available packages

Maxlona.FeatureFlags

NuGet · .NET · v1.4.1· .NET 8.0 or later
Available

The online .NET SDK: evaluation with typed variants and decision explanations, plus the complete public Management API, over a direct call to Maxlona. Register with a single key and environment; the SDK owns the service URL and HTTP transport. There is only one kind of key — a Management Key — and it can carry one or more permission levels (CanEvaluate, CanRead, CanWrite), so the same key setting can back both the evaluation client and the management client, or you can issue narrower keys per service. Resilient by default: Polly retries with exponential backoff, per-attempt timeouts, plain-language firewall and proxy diagnostics, and its own Serilog file log. Use IFeatureFlags and IFeatureFlagManagement in your application. Prefer Maxlona.FeatureFlags.Offline instead if you want evaluation to keep working, from an encrypted local cache, while Maxlona is unreachable.

Install

dotnet add package Maxlona.FeatureFlags --version 1.4.1

A newly published version takes a few minutes to be indexed by NuGet.org. If a version we have just announced is not found yet, wait and retry, or run dotnet restore --no-cache to bypass a stale local index. Previously published versions are unaffected.

Package details

Package IDMaxlona.FeatureFlags
Latest version1.4.1
Published byMaxlona
Target frameworknet8.0 — runs on .NET 8, 9, and 10
DependenciesMicrosoft.Extensions.Http/Hosting 8.0.1, Polly.Core 8.5.2, Serilog 4.2.0
LicenseMaxlona Proprietary SDK License, all rights reserved.

Evaluate a flag

using Maxlona.FeatureFlags;
using Maxlona.FeatureFlags.Models;
using Microsoft.Extensions.DependencyInjection;

// A Management Key with CanEvaluate permission is enough for this client.
var services = new ServiceCollection();
services.AddMaxlonaFeatureFlags(
    Environment.GetEnvironmentVariable("MAXLONA_MANAGEMENT_KEY")!, "production");
services.AddTransient<CheckoutService>();

using var provider = services.BuildServiceProvider();
var checkout = provider.GetRequiredService<CheckoutService>();
Console.WriteLine(await checkout.UseNewCheckoutAsync("user-123", "pro"));

// Inject the service into your application. No URL or HTTP client setup.
public sealed class CheckoutService(IFeatureFlags flags)
{
    public Task<bool> UseNewCheckoutAsync(
        string userId, string plan, CancellationToken ct = default) =>
        flags.IsEnabledAsync("checkout-redesign", new EvaluationContext
        {
            UserId = userId,
            Attributes = new Dictionary<string, object?> { ["plan"] = plan }
        }, defaultValue: false, cancellationToken: ct);
}

Using the SDK without dependency injection

Create a FeatureFlags service with your key and environment. The SDK handles the endpoint, authentication, retries, and transport. Logging works by default; the directory override below is optional. Use FeatureFlagManagement with a Management Key for administration.

using Maxlona.FeatureFlags;
using Maxlona.FeatureFlags.Models;

// Reuse for the application lifetime; dispose at shutdown. There is no
// service URL to configure — set options.BaseUri only for a proxy or a
// private deployment.
using var flags = new FeatureFlags(
    Environment.GetEnvironmentVariable("MAXLONA_MANAGEMENT_KEY")!, "production",
    options => options.Logging.Directory = "logs/flags"); // optional

var context = new EvaluationContext { UserId = "user-123" };
var enabled = await flags.IsEnabledAsync("checkout-redesign", context, defaultValue: false);
var limit = await flags.GetValueAsync<int>("checkout-limit", context, defaultValue: 10);
var attempt = await flags.TryEvaluateAsync("checkout-redesign", context, explain: true);
Console.WriteLine(attempt.Result?.Reason?.Message ?? attempt.Error?.Message);

// FeatureFlagManagement uses the same key when it carries CanRead/CanWrite,
// or a separate, narrower key when you want to keep evaluation and
// administration apart.
using var management = new FeatureFlagManagement(
    Environment.GetEnvironmentVariable("MAXLONA_MANAGEMENT_KEY")!);
var report = await management.CheckConnectivityAsync();
Console.WriteLine($"healthy: {report.IsHealthy} — {report.Summary}");

Managing flags from code (.NET)

There is only one kind of key — a Management Key — and it can carry one or more permission levels: CanEvaluate, CanRead, CanWrite. Use the same key for both clients when it carries every permission you need, or issue a narrower key per service so neither carries more authority than the code path using it.

Ready to run.NET 8+

Download the .NET consumer sample

A complete dependency-injection console app built with the working, published Maxlona.FeatureFlags 1.4.1 NuGet package. Every sample is a small class taking the Maxlona services it needs through its constructor: evaluation, typed variants, graceful degradation, reading and administering flags through the Management API, and the same API with no container at all.

ZIP archiveIncludes README and appsettings templateNo service URL to configure
Download sample

Create a flag and roll it out

using Maxlona.FeatureFlags;
using Maxlona.FeatureFlags.Models;
using Microsoft.Extensions.DependencyInjection;

// Use a Management Key with CanWrite permission for these operations.
var services = new ServiceCollection();
services.AddMaxlonaFeatureFlagManagement(
    Environment.GetEnvironmentVariable("MAXLONA_MANAGEMENT_KEY")!);

using var provider = services.BuildServiceProvider();
var management = provider.GetRequiredService<IFeatureFlagManagement>();

var created = await management.CreateAsync(new CreateFeatureFlagRequest
{
    Name = "checkout-redesign",
    Project = "Main App",
    Type = "release",
    Description = "New checkout flow",
    Variants =
    [
        FlagVariant.Create("on", true, "Feature enabled"),
        FlagVariant.Create("off", false, "Feature disabled")
    ]
});

// All users reach targeting rules; pro users receive the on variant.
// Users who match no rule receive the default off variant.
await management.UpsertConfigurationAsync(created.Name, new UpsertFlagConfigurationRequest
{
    Stage = "production",
    Enabled = true,
    DefaultVariantKey = "off",
    RolloutPercentage = 100,
    Rules =
    [
        new TargetingRule
        {
            Id = "pro-users",
            Description = "Pro plan customers",
            Priority = 10,
            Conditions = [TargetingCondition.Create("plan", "==", "pro")],
            Allocations = [new VariantAllocation { VariantKey = "on", Percentage = 100 }]
        }
    ]
});

// Read or replace the environments this flag uses. A change that would leave a
// dependency without a matching environment is rejected with a 409 unless you
// set DependencyResolution to "align" or "remove_dependencies".
var current = await management.GetEnvironmentsAsync(created.Name);
var updated = await management.UpdateEnvironmentsAsync(created.Name, new UpdateFlagEnvironmentsRequest
{
    Environments = ["dev", "production"]
});
Console.WriteLine($"{string.Join(", ", current.Environments)} -> {string.Join(", ", updated.Environments)}");

.NET API surface

IFeatureFlags

MethodReturnsNotes
EvaluateAsync(flagKey, context, explain, ct)EvaluationResultFull result. Pass explain: true for the decision reason. Throws if unreachable.
TryEvaluateAsync(flagKey, context, explain, ct)EvaluationAttemptReturns the result or service failure. Caller cancellation and invalid arguments still throw.
IsEnabledAsync(flagKey, context, ct)boolTrue when the variant key is neither "off" nor a false/null value.
IsEnabledAsync(flagKey, context, defaultValue, ct)boolFalls back to your default when the service is unreachable or refuses.
GetValueAsync<T>(flagKey, context, ct)T?Deserializes the variant value into your own type.
GetValueAsync<T>(flagKey, context, defaultValue, ct)T?Falls back to your default on failure or a type mismatch.

IFeatureFlagManagement

MethodReturnsPermission
ListAsync(ct)IReadOnlyList<FeatureFlag>CanRead
GetAsync(name, ct)FeatureFlagCanRead
TryGetAsync(name, ct)FeatureFlag?CanRead
ExistsAsync(name, ct)boolCanRead
GetEnvironmentsAsync(name, ct)FlagEnvironmentsCanRead
CreateAsync(request, ct)FeatureFlagCanWrite
UpdateAsync(name, request, ct)FeatureFlagCanWrite
UpdateEnvironmentsAsync(name, request, ct)FlagEnvironmentsCanWrite
ArchiveAsync(name, ct)FeatureFlagCanWrite
RestoreAsync(name, ct)FeatureFlagCanWrite
DeleteAsync(name, ct)voidCanWrite
UpsertConfigurationAsync(name, request, ct)FlagConfigurationCanWrite
CheckConnectivityAsync(ct)ConnectivityReportCanRead

Retries and timeouts

Every call runs through a Polly pipeline: three retries by default with exponential backoff and jitter, plus a per-attempt timeout of ten seconds. A 429's Retry-After is honoured, capped by Retry.MaxDelay. All of it applies whether the client came from dependency injection or was constructed by hand.

FailureReads, updates, deletes, upserts, evaluationsCreateAsync
Connection refused, DNS failure, host unreachableRetriedRetried — nothing was delivered
408, 429, 503RetriedRetried — the server declined it
500, 502, 504RetriedNot retried — the write may have landed
Connection reset mid-flight, timeoutRetriedNot retried
400, 401, 403, 404, 409Never retriedNever retried
Creating a flag twice is not the same as creating it once. A create is repeated only when the SDK can prove the request never reached the server — the connection was refused, or the API explicitly declined it. An ambiguous 500 stops at the first attempt rather than risking a duplicate.

Degrading gracefully

A feature flag is a configuration lookup, and an outage in a configuration lookup should not take down the feature it configures. Pass a fallback and an unreachable Maxlona stops being an exception: the failure is logged and your default wins. The overloads without a fallback still throw, for callers that want to know.

Errors and diagnostics

An API that answered and refused throws FeatureFlagApiException, carrying StatusCode, ErrorCode, TraceId, ResponseBody, and RetryAfter. An API that could not be reached at all throws FeatureFlagConnectionException instead, with a classified Problem and a plain-language Guidance string you can paste into a network ticket.

The distinction matters because a corporate network breaks an SDK in ways that all look identical from .NET — every one arrives as the same opaque HttpRequestException.

ProblemDetected fromWhat to check
DnsFailureThe host name did not resolveCheck DNS from the machine and look for a resolver that blocks unknown domains.
BlockedByFirewallConnection refused, reset, unreachable, or silently droppedAllow outbound HTTPS to the host and port in the host firewall, the network ACL, and any NSG or security group. Set HTTPS_PROXY if a proxy is in use.
TlsInterceptionThe TLS handshake was rejectedA TLS-inspecting proxy is re-signing traffic. Trust its root certificate, or bypass maxlona.com from inspection.
ProxyAuthenticationRequiredHTTP 407A proxy wants credentials. Maxlona itself never returns 407.
InterceptedResponseA non-JSON body on a 2xxA captive portal or web filter answered instead of Maxlona.
TimeoutThe attempt timeout elapsedNothing answered — the signature of a firewall that drops rather than refuses.

Call CheckConnectivityAsync at startup to test the whole path without throwing. A report that is unhealthy with no Problem means the network is fine and the API refused — a revoked key, a scope mismatch, or an inactive subscription. A Problem means the traffic never got there.

Local logging

The SDK keeps its own Serilog file log, on by default, recording every retry, every classified failure, and the guidance above. It never touches Serilog.Log.Logger, so switching it on cannot disturb your application's own logging — or set Logging.Logger to fold the SDK's events into it deliberately.

Files land in a maxlona-FFlags folder beside your running application (AppContext.BaseDirectory), rolling daily and keeping seven files of up to 16 MB. Set Logging.Directory to choose an absolute path or a relative path such as logs/flags. Relative paths resolve beside the executable. Set Logging.Enabled = false to disable file logging. An unwritable directory skips logging without redirecting it elsewhere.

Logging is best-effort by design. If the folder cannot be created or the file cannot be opened, the SDK degrades to writing nothing rather than failing the call — losing a log line must never cost a flag evaluation.

Using another language?

Download the HTTP Postman collection to learn how to evaluate feature flags through standard HTTPS GET and POST requests. This approach works with Node.js, React, Angular, .NET, Python, Java, PHP, Kotlin, Swift, Go, and any other platform that can send an HTTP request. The collection includes the endpoints, request payloads, authentication header, and response shapes you need to reproduce the calls in your application. For browser applications such as React or Angular, send the request through your backend so the Management Key is never exposed in client-side code.