Skip to Content
This documentation is provided with the HEAT environment and is relevant for this HEAT instance only.
APIHEAT.Common Client (C#)

HEAT.Common Client (C#)

HEAT.Common is a .NET library that provides a single, developer-friendly client for uploading data to HEAT, managing sessions, and exploring an environment programmatically. You configure where HEAT is and how to sign in (token or username/password); the client handles authentication, health checks, V2 API calls for ingestion, and Core API calls for read/exploration.

This page is the main documentation for using the HEAT.Common runtime. For a runnable end-to-end example, see Example project and the runtimes/csharp/HEAT.Common.Example project in the repo.


Overview

What HEAT.Common does

  • Connects to a HEAT instance (external URL or in-cluster) and validates connectivity via the V2 health endpoint.
  • Authenticates using a long-lived token (e.g. offline token) or username/password; with password auth, the client obtains and caches access/refresh tokens and retries on 401.
  • Creates and manages sessions , create a session in a project (with optional session template), create or join shared sessions, close shared sessions, and fetch session details.
  • Uploads data , send raw bytes (files or streams) to a node instance in a session; HEAT stores the data and creates a node output.
  • Enumerates metadata , list session templates, projects, platform configuration by prefix, and node outputs so you can discover templates, pick projects, read config from the environment, and verify uploads.
  • Explores environments , navigate projects, sessions, node instances, and node outputs via the Core API with lazy, paginated enumeration and on-demand output byte fetch.

You do not need to call HEAT Auth, V2, or Core API endpoints directly for typical integrator flows; the client wraps those behind a simple IHeatClient interface.

When to use HEAT.Common

Use HEAT.Common when you are building a .NET application (e.g. console app, web API, worker, or simulator integration) that needs to:

  • Push data into HEAT (create a session and upload data to node instances).
  • Create or join shared sessions and close them when done.
  • Discover session templates, projects, or platform configuration before creating sessions.
  • Build standalone tools or one-off scripts that list sessions in a project, inspect node state, and read node output payloads.

If you are building a runner (containerized processor that runs inside HEAT and processes node tasks), use the HEAT runtime for your language (e.g. Python heat-runtime) instead; runners are invoked by HEAT and do not use HEAT.Common to create sessions.


Prerequisites

  • .NET 8.0 (or the target framework your app uses; HEAT.Common targets net8.0).
  • A HEAT instance with the V2 API and HEAT Auth reachable (either from your machine for external use, or from inside the cluster for in-cluster use).
  • Authentication: either a long-lived token (e.g. offline token) or username and password for HEAT Auth.
  • At least one project and one session template in HEAT if you want to create sessions and upload data (you can create these in Cluster Manager or via the API).

Installation

HEAT.Common is distributed as a project reference from the HEAT repo (or as a built assembly). There is no public NuGet package at this time.

Add the project reference

  1. Clone or copy the HEAT repo (or the runtimes/csharp folder).
  2. In your .NET project, add a reference to HEAT.Common:
<ItemGroup> <ProjectReference Include="path/to/HEAT/runtimes/csharp/HEAT.Common/HEAT.Common.csproj" /> </ItemGroup>
  1. Add the packages required for dependency injection and HTTP (if not already present):
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />

HEAT.Common itself references Microsoft.Extensions.Http, Microsoft.Extensions.Options, and Microsoft.Extensions.Configuration.Binder; your app typically needs the DI and configuration packages (e.g. Microsoft.Extensions.Hosting or Microsoft.Extensions.DependencyInjection) to call AddHeatClient.


Quick start

  1. Register the client with your service collection (e.g. in Program.cs or Startup.cs):
using HEAT.Common; // Option A: from configuration (e.g. appsettings.json "Heat:Client" section) services.AddHeatClient(Configuration); // Option B: configure in code services.AddHeatClient(opts => { opts.BaseUrl = "https://your-env.heatvr.io"; opts.Token = "your-offline-or-access-token"; });
  1. Resolve and use the client:
var client = serviceProvider.GetRequiredService<IHeatClient>(); await client.ConnectAsync(); // optional: validate connectivity (or rely on AutoConnect) var session = await client.CreateSessionAsync(projectId, new CreateSessionRequest { Name = "My Session", Email = "user@example.com", SimulationName = "MySim" }); if (session?.NodeInstances?.Count > 0) { await using var data = new MemoryStream(Encoding.UTF8.GetBytes("your payload")); var output = await client.UploadNodeOutputAsync(session.NodeInstances[0].Id, data); }

Authentication

HEAT.Common supports two authentication modes. You must set one; the client will throw HeatClientConfigurationException if neither is configured.

Token authentication

Use a long-lived token (e.g. an offline token) when your app runs without user interaction or when you want to avoid storing username/password.

  • Set HeatClientOptions.Token (and BaseUrl for external HEAT).
  • The client sends the token on every V2 request; no refresh is performed.
  • For in-cluster use (e.g. a pod inside the same Kubernetes cluster as HEAT), use HeatClientOptions.ForInClusterToken("your-token") so the client targets http://heat-v2-api:5000/api/v2 and http://heat-auth without a base URL.

Example (external):

services.AddHeatClient(opts => { opts.BaseUrl = "https://your-env.heatvr.io"; opts.Token = "your-offline-token"; });

Example (in-cluster):

services.AddHeatClient(opts => { opts.InCluster = true; opts.Token = "your-token"; });

Username and password

Use username and password when you have interactive or service credentials. The client will:

  • Call HEAT Auth to obtain an access token (and optionally a refresh token).
  • Cache the access token and send it on every V2 request.
  • On 401 Unauthorized, attempt to refresh the token once and retry the request (for non-streaming calls).

Set HeatClientOptions.Username and HeatClientOptions.Password; optionally set RememberMe = true (default) to request a refresh token.

services.AddHeatClient(opts => { opts.BaseUrl = "https://your-env.heatvr.io"; opts.Username = "service-account"; opts.Password = "secret"; });

In-cluster vs external

ScenarioBaseUrlInClusterAuth
App outside HEAT (e.g. your server)HEAT ingress URL (e.g. https://your-env.heatvr.io)falseToken or Username/Password
App inside HEAT cluster (e.g. another service)Not requiredtrueToken (or Username/Password; auth service is http://heat-auth)

Registration (dependency injection)

Register the client with one of the following.

From configuration

Bind options from the Heat:Client section (e.g. in appsettings.json):

{ "Heat": { "Client": { "BaseUrl": "https://your-env.heatvr.io", "Token": "your-token" } } }
services.AddHeatClient(Configuration);

From code

Configure options in code (e.g. from environment variables):

services.AddHeatClient(opts => { opts.BaseUrl = Environment.GetEnvironmentVariable("HEAT_BASE_URL"); opts.Token = Environment.GetEnvironmentVariable("HEAT_TOKEN"); // or: opts.Username = ...; opts.Password = ...; });

The client is registered as a singleton (IHeatClient / HeatClient) and uses a named HttpClient ("HeatClient"). Do not register multiple IHeatClient unless you use different option instances and named clients.


API reference

All methods are on IHeatClient and are async. Cancellation is supported via CancellationToken; omit it or pass default if not needed.

Connect and health

ConnectAsync(CancellationToken ct = default)

Validates that the HEAT environment is reachable. For external HEAT, calls GET /api/v2/health; 200 OK means the environment is OK. Call this explicitly if HeatClientOptions.AutoConnect is false; otherwise the client will connect on first use of any session/upload method.

Throws: HeatConnectionException if the environment is not reachable or did not return OK.

await client.ConnectAsync();

Discovery (templates, config, projects)

GetSessionTemplatesAsync(CancellationToken ct = default)

Returns all session templates. Use a template’s Id in CreateSessionRequest.SessionTemplateId when creating a session.

Returns: IReadOnlyList<SessionTemplateInfo> (Id, Name, Description, Configuration).

var templates = await client.GetSessionTemplatesAsync(); foreach (var t in templates) Console.WriteLine($"Template {t.Id}: {t.Name}");

GetPlatformConfigurationByPrefixAsync(string prefix, CancellationToken ct = default)

Returns platform configuration keys whose name equals or starts with the given prefix (e.g. "public_api_v1"). Useful for reading config values from the environment without hardcoding. External users are restricted to prefixes allowed by HEAT (e.g. public_api_v1).

Returns: IReadOnlyDictionary<string, object>. Empty if no keys match or the API returns 404.

var config = await client.GetPlatformConfigurationByPrefixAsync("public_api_v1"); if (config.TryGetValue("SomeKey", out var value)) Console.WriteLine(value);

GetProjectsAsync(CancellationToken ct = default)

Returns all projects. Use a project’s Id as projectId when calling CreateSessionAsync or CreateOrJoinSharedSessionAsync.

Returns: IReadOnlyList<ProjectInfo> (Id, HexId, Title, Description, DefaultSessionTemplateId).

var projects = await client.GetProjectsAsync(); int projectId = projects[0].Id;

Sessions

CreateSessionAsync(int projectId, CreateSessionRequest request, CancellationToken ct = default)

Creates a new session for the given project. You can set request.SessionTemplateId to use a specific template, or leave it null to use the project’s default template.

Returns: SessionInfo (Id, Name, Email, ProjectId, SessionTemplateId, SimulationName, NodeInstances). Use NodeInstances[].Id to upload data. Returns null only if the API returns no data.

Throws: HeatApiException on 404 (e.g. project not found), 400, etc.

var session = await client.CreateSessionAsync(projectId, new CreateSessionRequest { Name = "Training run 001", Email = "user@example.com", SimulationName = "MySimulator", SessionTemplateId = 2 // optional }); Guid sessionId = session.Id; var firstNodeId = session.NodeInstances?[0].Id;

CreateOrJoinSharedSessionAsync(int projectId, CreateSharedSessionRequest request, CancellationToken ct = default)

Creates a new shared session or joins an existing one within a time window (based on simulation name, machine group, etc.). Use this for scenarios where multiple clients contribute to the same logical session.

Returns: SessionInfo (new or existing session with node instances).

var session = await client.CreateOrJoinSharedSessionAsync(projectId, new CreateSharedSessionRequest { Name = "Shared session", SimulationName = "Sim", Email = "user@example.com", SessionTemplateId = 1, StartTimeUtc = DateTime.UtcNow, MachineGroup = 1, TimeWindowSeconds = 300 });

GetSessionAsync(Guid sessionId, CancellationToken ct = default)

Fetches a session by ID. Returns the session with node instances, or null if not found.

var session = await client.GetSessionAsync(sessionId);

CloseSessionAsync(Guid sessionId, CloseSessionRequest request, CancellationToken ct = default)

Closes a shared session. Idempotent; call when the shared session is finished.

Returns: true if the close succeeded.

await client.CloseSessionAsync(sessionId, new CloseSessionRequest { Email = "user@example.com", Reason = "Completed", CloseAt = DateTime.UtcNow });

Upload and outputs

UploadNodeOutputAsync(int nodeInstanceId, Stream data, CancellationToken ct = default)

Uploads raw data to a node instance. The node instance must be configured with a data source (e.g. S3-compatible storage); HEAT stores the stream and creates a node output. Use a node instance ID from SessionInfo.NodeInstances (e.g. after creating or fetching a session).

Returns: NodeOutputInfo (Id, NodeInstanceId, CreatedAt, DataPath, etc.) or null if the API returned no data.

Throws: HeatApiException on 404 (node not found), 400 (e.g. invalid node configuration), etc.

await using var stream = File.OpenRead("data.json"); var output = await client.UploadNodeOutputAsync(nodeInstanceId, stream); Console.WriteLine($"Created output {output?.Id} at {output?.CreatedAt}");

GetNodeOutputsAsync(int nodeInstanceId, CancellationToken ct = default)

Returns all outputs (uploaded data) for the given node instance, ordered by creation time. Use this to verify that data was uploaded. Returns an empty list if the node has no outputs or the API returns 404.

Returns: IReadOnlyList<NodeOutputInfo>.

var outputs = await client.GetNodeOutputsAsync(nodeInstanceId); foreach (var o in outputs) Console.WriteLine($"Output {o.Id} at {o.CreatedAt}");

Models (DTOs)

TypePurpose
CreateSessionRequestName, Email, SimulationName (required); SessionTemplateId, Metadata (optional).
CreateSharedSessionRequestName, SimulationName, Email, SessionTemplateId, StartTimeUtc, MachineGroup (required); TimeWindowSeconds (optional, default 300).
CloseSessionRequestEmail, Reason, CloseAt (required).
SessionInfoId, Name, Email, ProjectId, SessionTemplateId, SimulationName, NodeInstances (list of NodeInstanceInfo).
NodeInstanceInfoId, Name. Use Id to upload data or list outputs.
NodeOutputInfoId, NodeInstanceId, Configuration, CreatedAt, DataSourceId, DataPath, OutputIdentifier.
SessionTemplateInfoId, Name, Description, Configuration. Use Id in CreateSessionRequest.SessionTemplateId.
ProjectInfoId, HexId, Title, Description, DefaultSessionTemplateId. Use Id as projectId when creating sessions.

All request types live in the HEAT.Common.Models namespace and match the HEAT V2 API shape (camelCase in JSON).


Configuration reference (Heat:Client)

When using AddHeatClient(Configuration), options are bound from the Heat:Client section:

KeyDescription
BaseUrlBase URL for external HEAT (e.g. https://your-env.heatvr.io). Required when not in-cluster. Do not include a trailing slash.
InClusterWhen true, use in-cluster defaults (V2 API at http://heat-v2-api:5000/api/v2, Auth at http://heat-auth).
TokenLong-lived token (e.g. offline token). Use this or Username+Password.
UsernameUsername for password auth.
PasswordPassword for password auth.
RememberMeWhen using username/password, request a refresh token (default true).
AutoConnectWhen true (default), the client runs the health check on first use if you have not called ConnectAsync() already. Set to false to require an explicit ConnectAsync() before any other operations.

Example project

A full working example that demonstrates the entire upload workflow is in the repo at runtimes/csharp/HEAT.Common.Example. It:

  1. Instantiates a HEAT client from environment variables.
  2. Connects and enumerates session templates and (optionally) platform config.
  3. Lists projects and picks one (or uses HEAT_PROJECT_ID).
  4. Creates a session with the desired (or default) template.
  5. Uploads a small text payload to the first node instance.
  6. Lists outputs on that node instance to verify the upload.

How to run the example

Prerequisites: A running HEAT instance, and either a token or username/password.

Set environment variables, then run:

export HEAT_BASE_URL=https://your-env.heatvr.io export HEAT_TOKEN=your-offline-token # Or: export HEAT_USERNAME=...; export HEAT_PASSWORD=... dotnet run --project runtimes/csharp/HEAT.Common.Example

Optional:

  • HEAT_PROJECT_ID , use this project ID instead of the first in the list.
  • HEAT_SESSION_TEMPLATE_ID , use this template when creating the session; otherwise the project’s default is used.
  • HEAT_CONFIG_PREFIX , if set, the example fetches and prints platform config for this prefix (e.g. public_api_v1).

See runtimes/csharp/HEAT.Common.Example/README.md in the repo for full details.


Environment exploration (Core API)

Use exploration when you need to read from a HEAT environment at scale: enumerate thousands of sessions, walk node instances, and fetch output bytes on demand. Exploration uses the Core API (/api/...) on the same base URL as HEAT Auth (for external use, the ingress URL that reaches Core API, same as coreApiUrl in cli.settings.json).

Domain model

IHeatClient returns navigable objects in the HEAT.Common.Environment namespace:

TypeRole
HeatProjectProject metadata; EnumerateSessionsAsync() pages through sessions
HeatSessionSession with name, GUID, template, metadata, users, dimensions, scenarios; EnumerateNodesAsync() lists nodes
HeatNodeInstanceNode state, template, last processed; Outputs for output metadata
HeatNodeOutputOutput id and metadata; GetBytesAsync() fetches payload from Core API
HeatMetadataFreeform session metadata with TryGetString, TryGetInt, TryGetBool using dotted JSON paths

Output metadata is enumerated lazily (most recent first). Payload bytes are not fetched until you call GetBytesAsync() on a HeatNodeOutput.

Permissive JSON

Exploration DTOs tolerate minor API drift across environments: missing keys map to defaults, extra keys are ignored, and inconsistent response wrappers ({ template: ... } vs direct arrays) are handled internally.

Standalone enumeration example

using HEAT.Common; using HEAT.Common.Environment; using Microsoft.Extensions.DependencyInjection; var services = new ServiceCollection(); services.AddHeatClient(HeatClientOptions.ForToken("https://your-env.heatvr.io", "your-token")); var client = services.BuildServiceProvider().GetRequiredService<IHeatClient>(); await client.ConnectAsync(); var project = (await client.GetEnvironmentProjectsAsync()) .First(p => p.Title?.Contains("MyProject", StringComparison.OrdinalIgnoreCase) == true); await foreach (var session in project.EnumerateSessionsAsync()) { await foreach (var node in session.EnumerateNodesAsync()) { if (node.LastState != HeatNodeState.ProcessingSucceeded) continue; if (!node.NameContains("timeline")) continue; var latest = await node.Outputs.GetLatestAsync(); if (latest is null) continue; var bytes = await latest.GetBytesAsync(); // process bytes (JSON, heatx, etc.) } }

Exploration entry points

MethodDescription
GetEnvironmentProjectsAsync()All projects as HeatProject
TryGetProjectAsync(projectId)Single project, or null
TryGetSessionAsync(sessionId)Full session detail from Core API, or null
HeatProject.EnumerateSessionsAsync()Paginated session list for the project
HeatProject.TryGetSettingsAsync()Project settings when supported; null on 404
HeatSession.EnumerateNodesAsync()Node instances (includeOutputs=false on the wire)
HeatSession.RefreshAsync()Re-fetch users, dimensions, scenarios, metadata
HeatNodeOutputCollection.GetLatestAsync()Latest output metadata only
HeatNodeOutput.GetBytesAsync(raw: false)Output payload via GET /api/node-instances/outputs/{id}/data

Pagination options

Configure on HeatClientOptions:

  • SessionPageSize (1 to 100, default 100) for session enumeration
  • OutputPageSize (default 100) for output metadata pages
  • CoreApiUrl (optional) when Core API is on a different host than the V2 ingress

End-to-end workflow (upload data)

A typical flow to upload data to HEAT:

  1. Connect , await client.ConnectAsync(); (or rely on AutoConnect).
  2. Discover , Optionally call GetSessionTemplatesAsync(), GetPlatformConfigurationByPrefixAsync(prefix), and GetProjectsAsync() to choose a project and template.
  3. Create session , CreateSessionAsync(projectId, new CreateSessionRequest { ... }). Use SessionTemplateId from a template or leave null for the project default.
  4. Get node instance , From session.NodeInstances, pick the node (e.g. the first input node) and use its Id.
  5. Upload , UploadNodeOutputAsync(nodeInstanceId, stream).
  6. Verify , GetNodeOutputsAsync(nodeInstanceId) to confirm the new output appears.
  7. Close (if shared session) , CloseSessionAsync(sessionId, new CloseSessionRequest { ... }).

Error handling

The client throws explainable exceptions; the message describes what went wrong and what to try next.

ExceptionWhen it is thrownWhat to do
HeatClientConfigurationExceptionInvalid options (e.g. neither Token nor Username+Password set; BaseUrl missing when not InCluster).Set either Token or both Username and Password. Ensure BaseUrl is set when InCluster is false.
HeatConnectionExceptionHealth check or connectivity failed (GET /api/v2/health non-200 or network error).Check HEAT base URL, network/firewall, and that the V2 API is running. For external use, use the ingress URL.
HeatAuthenticationExceptionAuth failed (invalid or expired token, wrong username/password, 401 from auth or V2).For token: obtain a new or offline token. For username/password: check credentials.
HeatApiExceptionV2 API returned 4xx/5xx for a session or upload operation.Inspect Operation, StatusCode, and optional ResponseBody. Fix the request (e.g. valid projectId, node instance from the session).

Example:

try { await client.ConnectAsync(); var session = await client.CreateSessionAsync(projectId, request); // ... } catch (HeatConnectionException ex) { _logger.LogError(ex, "HEAT is not reachable"); } catch (HeatAuthenticationException ex) { _logger.LogError(ex, "Authentication failed"); } catch (HeatApiException ex) { _logger.LogError(ex, "API error: {Operation} {StatusCode}", ex.Operation, ex.StatusCode); }

Troubleshooting

SymptomLikely causeAction
”HEAT environment at … did not return OK”Wrong base URL or V2 API down.Verify base URL (no trailing slash). Ensure GET /api/v2/health returns 200 from your environment.
”Authentication failed”Invalid/expired token or wrong username/password.For token: get a new or offline token. For password: check credentials.
”CreateSession failed (404): Project not found”The projectId does not exist.Use an ID from GetProjectsAsync() or Cluster Manager.
”UploadNodeOutput failed (404)“Node instance ID invalid or not in the session.Use an ID from session.NodeInstances after creating or fetching the session.
”UploadNodeOutput failed (400)“Node configuration invalid (e.g. missing DataSourceName).Fix the session template / node template configuration in HEAT so the node has a valid data source.
Connection timeoutsApp outside cluster cannot reach HEAT.Use the public ingress URL; check firewall/DNS.
GetNodeOutputsAsync returns empty listNode has no outputs yet, or node not found (client treats 404 as empty).Confirm you uploaded to this node instance; confirm node instance ID is correct.
Exploration calls fail with 401/403Token invalid or Core API auth mode requires Bearer.Use a valid offline token or username/password. Ensure BaseUrl reaches Core API (same host as agentic-cli coreApiUrl).
Session enumeration returns few resultsDefault server page size is small if not paginated by client.Client paginates automatically; optionally lower SessionPageSize for testing.

Source code and example: runtimes/csharp/HEAT.Common and runtimes/csharp/HEAT.Common.Example in the HEAT repository.