Configuring the Management client

September 18, 2026 · View on GitHub

How to register a client, where its options come from, and what the transport does by default.

Client registration and lifetime

Two entry points, same builder: AddManagementClient for applications, ManagementClient.Create or the constructor for scripts.

using Kontent.Ai.Management.Extensions;

services.AddManagementClient(management => management.Options.Configure(options =>
{
    options.EnvironmentId = "<YOUR_ENVIRONMENT_ID>";
    options.ApiKey = "<YOUR_API_KEY>";
}));

AddManagementClient lives in Kontent.Ai.Management.Extensions; the registration snippets below assume that using. Inject IManagementClient wherever you need it.

The container owns a registered client — do not dispose it. A client you build yourself owns its resources, so dispose it:

await using var client = new ManagementClient(new ManagementOptions
{
    EnvironmentId = "<YOUR_ENVIRONMENT_ID>",
    ApiKey = "<YOUR_API_KEY>"
});

ManagementClient.Create takes the same builder as AddManagementClient, for when a script needs more than the constructor offers:

await using var client = ManagementClient.Create(management => management.Options.Configure(options =>
{
    options.EnvironmentId = "<YOUR_ENVIRONMENT_ID>";
    options.ApiKey = "<YOUR_API_KEY>";
}));

The concrete ManagementClient implements IDisposable and IAsyncDisposable; the IManagementClient interface does not, because a resolved client belongs to the container. Invalid options throw OptionsValidationException from either entry point.

The builder exposes Options (an OptionsBuilder<ManagementOptions>), HttpClient and SubscriptionHttpClient (the IHttpClientBuilder for each transport scope), ConfigureResilience, and Services / Name. Whatever you chain runs after the SDK's own setup.

Note

IManagementClientFactory resolves a named client from your container. ManagementClient.Create builds a standalone one. Similar names, different jobs.

Options and configuration binding

Bind from an IConfiguration section:

{
  "ManagementOptions": {
    "EnvironmentId": "<YOUR_ENVIRONMENT_ID>",
    "ApiKey": "<YOUR_API_KEY>"
  }
}
services.AddManagementClient(management =>
    management.Options.Bind(configuration.GetSection("ManagementOptions")));

Options is a standard OptionsBuilder<ManagementOptions>, so the rest of the options pattern is available unchanged:

Instead of BindUse
Section resolved from the container by namemanagement.Options.BindConfiguration("Management:Production")
A pre-built options instanceservices.AddManagementClient(new ManagementOptions { … })
Values from another registered servicemanagement.Options.Configure<ISecretStore>((options, secrets) => …)
Validation beyond the built-in rulesmanagement.Options.Validate(options => …)

A pre-built instance is copied onto the options the container materializes, so mutating it afterwards has no effect once they have been read.

Named clients

Give each registration a name to run more than one environment:

services.AddManagementClient("production", management => management.Options.Configure(options =>
{
    options.EnvironmentId = "<PRODUCTION_ENVIRONMENT_ID>";
    options.ApiKey = "<PRODUCTION_API_KEY>";
}));

services.AddManagementClient("staging", management => management.Options.Configure(options =>
{
    options.EnvironmentId = "<STAGING_ENVIRONMENT_ID>";
    options.ApiKey = "<STAGING_API_KEY>";
}));

Resolve them through IManagementClientFactory:

var production = clientFactory.Get("production");
var staging = clientFactory.Get("staging");

Names are compared ordinally. A client needing different credentials — a subscription key, for instance — is a separate named registration, not a second key on an existing one.

Configuration options

OptionRequiredDefaultDescription
EnvironmentIdFor environment endpointsThe GUID of your Kontent.ai environment. Required for everything except subscription-scoped endpoints.
ApiKeyYesA Management API key for environment-scoped endpoints, or a Subscription API key for subscription-scoped ones. They are different keys — see subscription-scoped operations.
SubscriptionIdFor subscription endpointsThe subscription GUID. Required only for subscription-scoped endpoints such as user management.
EnableResilienceNotrueToggles the built-in retry/backoff pipeline without uninstalling it.
TimeoutNo30 minutesCeiling on one call, covering every retry attempt and the waits between them.
EndpointNohttps://manage.kontent.aiThe Management API base address. Override only when targeting a non-production endpoint.

Options are validated on use. A missing ApiKey, a malformed identifier, or neither EnvironmentId nor SubscriptionId surfaces as OptionsValidationException — from the constructor and Create, or at host startup when the container validates.

Each scope is built only when its identifier is present, so a client scoped to one and used for the other fails immediately, naming the missing option, rather than sending a request to a path with an empty segment.

Retries and timeouts

Every client comes with a resilience pipeline (Microsoft.Extensions.Http.Resilience): exponential backoff with jitter, Retry-After handling, and idempotency-aware retries.

A 429 is retried for every method, because the request was rejected rather than applied. Other transient failures — 408, 5xx, transport errors — are retried only for idempotent methods (GET, HEAD, OPTIONS, PUT, DELETE). A POST or PATCH that fails mid-flight is never replayed, because the write may already have landed.

Set EnableResilience = false to make the pipeline a passthrough.

Tuning retries

Use TuneRetry to adjust the initialized default HttpRetryStrategyOptions before the SDK assembles the pipeline:

services.AddManagementClient(management =>
{
    management.Options.Configure(options => { options.EnvironmentId = "…"; options.ApiKey = "…"; });
    management.TuneRetry(retry => retry.MaxRetryAttempts = 5);
});

Changing the attempt count preserves the idempotency rule, Retry-After handling, backoff and jitter. Settings you leave alone follow the defaults of the SDK version you install. Assigning ShouldHandle or DelayGenerator explicitly replaces that part of the retry behavior; replacing ShouldHandle means taking responsibility for write safety.

Callbacks run in registration order with fresh options for each pipeline construction, on both the environment and subscription transports. The same hook works in ManagementClient.Create. It does not run when EnableResilience is false or ConfigureResilience replaces the pipeline. Management still has no default per-attempt timeout.

Replacing the pipeline

ConfigureResilience replaces the entire pipeline on both transports, including the idempotency rule. Use it when you need different strategies or ordering. Prefer TuneRetry for retry settings.

A bare new HttpRetryStrategyOptions() retries transient failures on every method, including POST and PATCH. DisableFor(HttpMethod.Post, HttpMethod.Patch) prevents those replays but also prevents retrying rate-limited writes; it is not equivalent to the SDK's rule.

Timeouts

Two clocks bound a request:

  • Per attempt — no default. Asset and file uploads can legitimately run long, and cutting one off only to retry it re-uploads the same bytes. Add one through ConfigureResilience if you want it.
  • The whole callManagementOptions.Timeout, covering every attempt and the waits between them. Defaults to 30 minutes, sized against the 2 GB asset limit.

Prefer Timeout for changing the total budget; replacing the pipeline is not the way to set one. Use Timeout.InfiniteTimeSpan to be bounded only by your CancellationToken. Timeout outranks Retry-After: the pipeline waits exactly as long as the server asked, but the call is still cut short if your ceiling runs out first.

Uploads have their own retry-safety rules — see supported sources and ownership.

HTTP handlers

Each transport scope has its own IHttpClientBuilder:

management.HttpClient.AddHttpMessageHandler<MyAuditingHandler>();              // environment-scoped
management.SubscriptionHttpClient.AddHttpMessageHandler<MyAuditingHandler>();  // subscription-scoped

Source tracking for tool authors

Every request carries two analytics headers:

  • X-KC-SDKID — identifies this SDK. Always nuget.org;Kontent.Ai.Management;<version>. Not configurable.
  • X-KC-SOURCE — identifies a library built on top of the SDK. Set only when a caller assembly opts in.

End-user applications need do nothing here. If you publish a library that wraps this SDK, add the attribute at assembly level; at request time the SDK walks the call stack, finds your assembly and reads it:

using Kontent.Ai.Management.Attributes;

[assembly: SourceTrackingHeader]

Two overloads override what it reports: SourceTrackingHeader("Acme.AwesomeTool") when your package id differs from your assembly name, and SourceTrackingHeader("Acme.AwesomeTool", 1, 2, 3, "beta") to pin the version too. Use one, not all three.