KeyInteractive.Common.Api
2.3.0
dotnet add package KeyInteractive.Common.Api --version 2.3.0
NuGet\Install-Package KeyInteractive.Common.Api -Version 2.3.0
<PackageReference Include="KeyInteractive.Common.Api" Version="2.3.0" />
<PackageVersion Include="KeyInteractive.Common.Api" Version="2.3.0" />
<PackageReference Include="KeyInteractive.Common.Api" />
paket add KeyInteractive.Common.Api --version 2.3.0
#r "nuget: KeyInteractive.Common.Api, 2.3.0"
#:package KeyInteractive.Common.Api@2.3.0
#addin nuget:?package=KeyInteractive.Common.Api&version=2.3.0
#tool nuget:?package=KeyInteractive.Common.Api&version=2.3.0
Key Interactive.Common.Api
KeyInteractive.Common.Api is the generic plumbing to build .NET clients for Key Interactive-style
APIs: a standard response envelope, an idempotent-only retry policy with exponential backoff, JWT
access + rotating refresh token handling, and the conventional Key Interactive request headers.
It ships no endpoints of its own — it's the base your own API consumer inherits from. For the
official client of the Key Interactive Users API, see
KeyInteractive.Common.Api.Users.
What's in it
KiApiResponse/KiApiResponseData— the standard response envelope (Success,ResponseCode,ErrorMessage, optionallyData). Every consumer method built on this package returns one of these — callers branch onSuccess, never on a caught exception: network/HTTP/JSON failures are turned into a synthetic error response, never thrown.ITokenProvider/TokenProvider— holds the JWT access + refresh token for one consumer instance. Register it Scoped in Blazor Server (one instance per circuit); never make an implementation ofITokenProviderstatic, or tokens leak between users of the same process after a SignalR reconnect. Also holds the storage name (SetStorageName/GetStorageName) this circuit uses for its session — see "Rotated refresh token storage" below.KiApiConsumerOptions— per-instance configuration (base URI, optional API key/application key, API version segment, token storage key prefix, device info, timeout).IKiApiConsumer— the minimal interface every consumer implements (SetSiteBaseUrl/SetSiteLanguage).KiApiConsumerBase— the abstract base class. Handles the HttpClient lifecycle, retry, token rotation, and conventional headers; exposesGetAsync/PostAsync/PutAsync/DeleteAsynchelpers so each endpoint in your derived consumer is a one-liner.
Requirements
Targets .NET 8.0 or later (net8.0, net10.0). Microsoft.JSInterop is referenced per-TFM
(8.0.x on net8.0, 10.0.x on net10.0) and resolved automatically by NuGet.
Installation
dotnet add package KeyInteractive.Common.Api
Building your own API consumer
using KeyInteractive.Common.Api;
public class MyApiConsumer : KiApiConsumerBase, IMyApiConsumer
{
public MyApiConsumer(HttpClient? httpClient = null, ITokenProvider? tokenProvider = null, IJSRuntime? jsRuntime = null)
: base(httpClient, new KiApiConsumerOptions
{
BaseUri = "https://my-api.example.com/",
ApiKey = "your-api-key", // optional: omit (leave null) to skip the header entirely
ApplicationKey = "your-app-id", // optional, same as above
ApiVersion = "v1", // path segment, defaults to "v1"
StorageKeyPrefix = "myapi", // must be unique per consumer in the same site
}, tokenProvider, jsRuntime)
{
}
public Task<APIThingResponse> GetThing(int id) =>
GetAsync<APIThingResponse>($"Thing/{id}");
// Not idempotent by default: safe against double-submission on retry.
public Task<KiApiResponse> DoStuff(StuffDto dto) =>
PostAsync<KiApiResponse>("Thing/DoStuff", dto);
// Pass isIdempotent: true only for endpoints that are genuinely safe to retry
// automatically (e.g. a read-only server-to-server lookup implemented as a POST).
public Task<APIThingResponse> LookupThing(LookupDto dto) =>
PostAsync<APIThingResponse>("Thing/Lookup", dto, isIdempotent: true);
}
APIThingResponse is any class deriving from KiApiResponse (or KiApiResponseData if you need
a generic payload wrapper).
Response envelope
Every call returns a KiApiResponse-derived type — check Success, never rely on an exception:
var result = await myApiConsumer.GetThing(42);
if (result.Success)
{
// use result.Thing (or whatever fields your APIThingResponse adds)
}
else
{
Console.WriteLine(result.ErrorMessage);
}
Dependency injection (Blazor Server)
// Program.cs
builder.Services.AddScoped<ITokenProvider, TokenProvider>();
builder.Services.AddHttpClient<IMyApiConsumer, MyApiConsumer>();
If a single site talks to two different APIs built on this package (e.g. the Users API plus
your own), register two distinct typed HttpClients and give each KiApiConsumerOptions its own
StorageKeyPrefix — otherwise both consumers would fight over the same
{prefix}_auth_token/{prefix}_auth_refresh_token storage keys. If the two APIs also have
independent login sessions, register two separate ITokenProvider implementations too (e.g. via
keyed services or two marker interfaces), rather than sharing one.
Behavior you can rely on
- Retry: automatic only for idempotent calls (
GetAsync, orPostAsync/PutAsync/DeleteAsyncexplicitly markedisIdempotent: true), with exponential backoff, and only on408/429/502/503/504or a network-levelHttpRequestException. Non-idempotent POSTs fail on the first error — retrying after the server already processed the request would risk duplicating its effect. - Refresh token rotation: applied from the response headers before branching on
success/error, since the API may rotate the refresh token on a request it still rejects (e.g.
wrong password). Optionally persisted to
localStorage/sessionStorageviaIJSRuntime, if one is provided. - Rotated refresh token storage: persisted to whichever storage
TokenProvider.GetStorageName()reports for the current circuit — calltokenProvider.SetStorageName("localStorage")or"sessionStorage"once, at login or at session bootstrap, based on your own "remember me" flag. Never re-derive it by probinglocalStoragefor an existing token:localStorageis shared by every tab of the same browser/domain, so a tab that only usessessionStoragecould find another tab's (another user's, another tenant's)localStoragesession and overwrite it with its own rotated token — a cross-tenant session leak. IfSetStorageNameis never called, the fallback is always"sessionStorage"(never"localStorage"), so an un-updated caller stays safe, just without persistence across tabs until it's updated. - 401 retry: a single automatic retry on
401 Unauthorized, even for non-idempotent calls — it rereads the token fromITokenProviderand retries once, to recover from a benign race between two concurrent calls on the same circuit racing the same refresh-token rotation (the request never reached application logic, so retrying is safe). A second consecutive 401 (a genuinely invalid token) is not retried again. - No exceptions from network/HTTP/JSON failures: they're translated into a normal
KiApiResponsewithSuccess = false. - Error message extraction: on a non-retryable HTTP error,
ErrorMessageis populated from the error body even when your API doesn't respond in the KI envelope shape ({"errorMessage": "..."}). If the body is instead an ASP.NET CoreProblemDetailsobject ({"title","status","detail"}— the .NET default for unhandled model-validation/exception responses),detailis used, thentitle; if neither shape matches, the raw body is returned as-is. Only kicks in when the KI-shapederrorMessagewould otherwise have been empty — an API that already populates it sees no change.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 is compatible. 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. |
-
net10.0
- KeyInteractive.Common (>= 2.1.0)
- Microsoft.JSInterop (>= 10.0.3)
-
net8.0
- KeyInteractive.Common (>= 2.1.0)
- Microsoft.JSInterop (>= 8.0.29)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on KeyInteractive.Common.Api:
| Package | Downloads |
|---|---|
|
KeyInteractive.Common.Api.Users
.NET client for the Key Interactive Users API: registration, login, enrollment, profile/password management, and the "Sign in with Key Interactive" authorize/consent/poll flow. |
|
|
KeyInteractive.Common.Api.Tenants
.NET client for platforms integrating Key Interactive tenant (business) accounts: tenant login, tenant identity/profile (including PartnerCode), and team management (invite/list/remove/role members). |
|
|
KeyInteractive.Messivo
.NET client for the Messivo WhatsApp Business Cloud API integration platform: send text/template/media messages, read conversations and message history, manage templates, phone numbers and contacts. |
GitHub repositories
This package is not used by any popular GitHub repositories.