.NET SDK
Consystence.Sdk is the .NET client library that the csy CLI is built on. It targets .NET 10, wraps the HTTP surface behind a set of strongly-typed clients, and is built for dependency injection (DI) first.
The SDK is not packaged for integrators. To integrate with Consystence from your own code, call the REST API directly with an API key. This page describes the library as the CLI uses it, and maps the route families the CLI drives.
The library is deliberately thin: it owns HTTP composition (a named HttpClient, auth and tenant message handlers, retry policy, JSON), and it mirrors the server’s wire contracts as SDK-local records so it never has to reference the server’s internal contract assemblies.
Packages
Section titled “Packages”Registering the client
Section titled “Registering the client”A host adds the SDK to an IServiceCollection with AddConsystenceClient (in the Consystence.Sdk.Extensions namespace); the CLI does this at startup. Two overloads are provided.
Configure options inline:
using Consystence.Sdk.Extensions;
builder.Services.AddConsystenceClient(options =>{ // The server for the current context: an org's cell-direct host for /api routes, // or the account host for account commands. options.BaseUrl = "https://acme.consystence.com"; options.TenantCode = "acme";});Or bind from configuration. With no argument it binds the Consystence section, validates data annotations, and validates on start:
builder.Services.AddConsystenceClient(); // binds "Consystence"{ "Consystence": { "BaseUrl": "https://acme.consystence.com", "TenantCode": "acme" }}Either overload registers everything needed: the named ConsystenceApi HttpClient, the message handlers, the default credential provider, every typed client as a singleton, and the IConsystenceClient aggregate. Resolve and use it:
var client = serviceProvider.GetRequiredService<IConsystenceClient>();
var sites = await client.Sites.ListAsync(cancellationToken);Configuration options
Section titled “Configuration options”ConsystenceClientOptions (section name Consystence):
| Property | Default | Purpose |
|---|---|---|
BaseUrl | set it (the built-in value is a placeholder, not a Consystence host) | Base address of the named ConsystenceApi client (all typed clients). |
TenantCode | required | Sent on every API request as X-Tenant-Code. |
MaxRetryAttempts | 3 | Polly transient-error retry count (idempotency-gated — see the HTTP pipeline). |
InitialRetryDelay | 500 ms | First retry delay; backoff doubles each attempt. |
HttpTimeout | 30 s | Per-request timeout. |
UserAgent | null → Consystence-Sdk/<version> | User-Agent sent on every request; hosts set it to identify themselves. |
The aggregate client
Section titled “The aggregate client”IConsystenceClient exposes the typed clients as properties. Each is also independently injectable.
| Property | Interface | Route family | Purpose |
|---|---|---|---|
Setup | ISetupClient | GET /api/setup/status | Server setup status (mode, version, activation state). |
Admin | IAdminClient | /api/admin/* | Org/tenant administration; tenant YAML import (cloud cell only), export (a cloud cell for the sites it hosts, or a site server for its own site) and restore (RestoreTenantAsync, on a site server); staged tenant import (preflight, plan, apply, discard); API keys (create, list, revoke); and KPI settings. |
Sites | ISiteClient | /api/sites, /api/kpi-definitions | Site create/list/get/update; delete and purge, each with a confirmation code; operator policy; and site operations (get, diff, set, clear). Operational reads: active alarms (ReadAlarmsAsync), alarm history, the command-audit ledger (ReadCommandsAsync), the logbook, KPIs and KPI definitions, the maintenance register, the simulation ledger and cloud sync status. Alarm acknowledgement (AcknowledgeAlarmAsync, which records the acknowledgement; nothing is written to a controller), logbook append (attributed to the signed-in person), and site simulation control (status, start, stop, reset, scenarios). |
Controllers | IControllerClient | /api/sites/{siteId}/controllers, /api/sites/{siteId}/controller-chains | Controller create/list/get/update/assign/decommission, plus each controller’s heartbeat chain (ListChainsAsync). |
Instances | IInstanceClient | /api/sites/{siteId}/instances | Device-instance create/list/get/update/delete, plus live reads: current tag values (ReadTagsAsync), a historian trend window (ReadTrendAsync), and the instance’s command audit (ReadCommandsAsync). |
Electrical | IElectricalClient | /api/sites/{siteId}/electrical | Power-distribution nodes, edges, and topology. |
Account | IAccountClient | /api/me, /api/tenants, /api/orgs/* (account host) | Account control-plane self-service (account-of-record, tenants, org/member/licence management). |
Config | IConfigClient | /api/admin/config, /api/admin/orgs/{slug}/config | Stored configuration overrides, for the server or a tenant: list/set/unset, audit trail, and reload (server only). |
Ai | IAiClient | /api/ai | AI plane (org-admin-gated): provider health (HealthAsync) and test completions (CompleteAsync). |
TrendImports | ITrendImportClient | /api/historian/import-batches | Bulk historian import: SubmitAsync uploads a trend CSV + tag-mapping CSV (a duplicate-file_hash 409 resolves to the existing batch rather than failing); GetStatusAsync polls a batch; DeleteAsync deletes the historian rows a batch wrote. |
Displays | IDisplayClient | /api/sites/{code}/displays | A site’s authored displays: list, get, diff, put, remove (an owner or admin of the site’s tenant). A display is presentation only and never issues a command. |
Authentication and credentials
Section titled “Authentication and credentials”The SDK sends one credential per request, never both:
- Bearer — a person’s access token, sent as
Authorization: Bearer …. The SDK has no login surface: its token store is internal and is filled by the CLI. The CLI signs in with OIDC against the platform’s identity provider (Entra External ID) and renews the token against its issuer — the identity provider for a hosted sign-in, or the site server’sPOST /auth/site/refreshfor a site-PIN session. See the CLI’s authentication. - API key — a tenant API key (
csk_prefix), sent asX-Api-Keyand never refreshed: the key is the credential.
Over the REST API, a read-only key reads, and an operator key can acknowledge alarms on its own site; no key creates or changes sites, controllers or displays. A change needs a person’s sign-in. See API keys and the authentication model.
The HTTP pipeline
Section titled “The HTTP pipeline”All typed clients use the named ConsystenceApi client, whose pipeline is:
flowchart LR A[Typed client<br/>e.g. Sites.ListAsync] --> B[Tenant header<br/>adds X-Tenant-Code] B --> C[Credential<br/>X-Api-Key XOR Bearer] C --> D[Polly retry<br/>transient HTTP errors] D --> E[Consystence server]
- Tenant header — every request that does not already carry
X-Tenant-CodegetsX-Tenant-Code: {TenantCode}. - Credential — the SDK asks the credential provider for the active credential. On the API-key path it clears any
Authorizationheader, setsX-Api-Key, and does no refresh or 401 retry. On the Bearer path it attachesAuthorization, and — if the token is expired or a401comes back and a refresh token exists — refreshes once and resends the request. - Polly wraps the named client with
HandleTransientHttpErrorand exponentialWaitAndRetryAsyncbackoff governed byMaxRetryAttemptsandInitialRetryDelay. - Both the Polly retry and the post-
401resend are idempotency-gated:GET/HEAD/OPTIONS/TRACE,PUT, andDELETErequests may always be re-sent; aPOSTorPATCHis re-sent only when it declares an idempotency contract — a stableIdempotency-Keyheader the endpoint honours, or an endpoint whose duplicate-submission409resolves to the existing resource. UndeclaredPOST/PATCHrequests are never retried, so a transient failure cannot duplicate a side effect.
Working with the typed clients
Section titled “Working with the typed clients”var all = await client.Sites.ListAsync(cancellationToken);var one = await client.Sites.GetAsync("CHPP", cancellationToken);The /api/sites endpoints return the bare SiteInfo record directly (no {site} envelope). With an API key, the site reads need an admin-role key. See the org and site model for what a site is.
Controllers
Section titled “Controllers”Controllers (the term Consystence uses, not “PLC”) are hand-created per site. The server mints each controller’s canonical ctl_… id — the request carries no id of your own. ControllerConnectionConfig defaults Port to 44818 (EtherNet/IP). ControllerPlatform records the controller’s family (ControlLogix, CompactLogix, Micro800, MicroLogix, PLCnext, Other, or Vplc for a virtual PLC). It is a model field, not a support statement: see controller communication for the drivers the platform ships.
Controllers.GetAsync returns the controller plus its live connection health. Controller records carry the controller’s network endpoint and are never readable with an API key; the CLI’s controller commands read and change them with a person’s sign-in.
Licences
Section titled “Licences”Licence issuance and revocation live in the account console — the cell API no longer mints or revokes licences, and the SDK has no dedicated licence client. Licences are read-only through two surviving paths, both with a person’s sign-in:
// Cell-direct: org details carry the licences inline.var org = await client.Admin.GetOrgAsync("acme", cancellationToken);// Account host: the org's licence pools and allocations.var licences = await client.Account.GetOrgLicencesAsync(orgId, cancellationToken);// licences.Pools, licences.Allocations, licences.AssignmentsSee licensing for the licence model.
Account
Section titled “Account”The account control-plane client targets the account host (the CLI scopes these to an account context with an account-audienced bearer).
var me = await client.Account.GetMeAsync(cancellationToken); // null if no account-of-record yetvar tenants = await client.Account.ListTenantsAsync(cancellationToken);GetMeAsync returns null on a 404 (authenticated, but no account row) rather than throwing — distinct from a 401, which throws.
IAccountClient also carries the org and member family (ListOrgsAsync, CreateOrgAsync, GetOrgAsync, GetOrgLicencesAsync, RequestOrgTierAsync, ProvisionUnderOrgAsync, plus invite and member management — mutations are Owner-only server-side).
JSON serialisation
Section titled “JSON serialisation”The SDK includes a source-generated JSON context, configured with the web defaults and string enums, so enums serialise as strings bidirectionally. It registers the account, licence, setup, site, and admin contract types for AOT-clean, reflection-free (de)serialisation.
The account client, the admin client’s org, member and domain endpoints, and the static platform-status client read through that generated context; the remaining clients currently use the plain web defaults.
Defensive deserialisation
Section titled “Defensive deserialisation”The SDK detects a non-JSON / HTML response — for example an SPA shell from a path that matched no route, or an endpoint the server’s baked mode does not serve — and surfaces a clean HttpRequestException naming the request URI, instead of a bare JsonException or a write that silently appears to succeed.
See also
Section titled “See also”- API overview and endpoint reference
- Authentication model and API keys
- The
csyCLI — the SDK’s host