HEAT.Common Client (C#)
HEAT.Common is a .NET library that provides a single, developer-friendly client for uploading data to HEAT and managing sessions. You configure where HEAT is and how to sign in (token or username/password); the client handles authentication, health checks, and calls to the HEAT V2 API so you can focus on creating sessions and uploading data.
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.
You do not need to call HEAT Auth or V2 endpoints directly; 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.
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
- Clone or copy the HEAT repo (or the
runtimes/csharpfolder). - In your .NET project, add a reference to
HEAT.Common:
<ItemGroup>
<ProjectReference Include="path/to/HEAT/runtimes/csharp/HEAT.Common/HEAT.Common.csproj" />
</ItemGroup>- 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
- Register the client with your service collection (e.g. in
Program.csorStartup.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";
});- 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(andBaseUrlfor 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 targetshttp://heat-v2-api:5000/api/v2andhttp://heat-authwithout 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
| Scenario | BaseUrl | InCluster | Auth |
|---|---|---|---|
| App outside HEAT (e.g. your server) | HEAT ingress URL (e.g. https://your-env.heatvr.io) | false | Token or Username/Password |
| App inside HEAT cluster (e.g. another service) | Not required | true | Token (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)
| Type | Purpose |
|---|---|
| CreateSessionRequest | Name, Email, SimulationName (required); SessionTemplateId, Metadata (optional). |
| CreateSharedSessionRequest | Name, SimulationName, Email, SessionTemplateId, StartTimeUtc, MachineGroup (required); TimeWindowSeconds (optional, default 300). |
| CloseSessionRequest | Email, Reason, CloseAt (required). |
| SessionInfo | Id, Name, Email, ProjectId, SessionTemplateId, SimulationName, NodeInstances (list of NodeInstanceInfo). |
| NodeInstanceInfo | Id, Name. Use Id to upload data or list outputs. |
| NodeOutputInfo | Id, NodeInstanceId, Configuration, CreatedAt, DataSourceId, DataPath, OutputIdentifier. |
| SessionTemplateInfo | Id, Name, Description, Configuration. Use Id in CreateSessionRequest.SessionTemplateId. |
| ProjectInfo | Id, 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:
| Key | Description |
|---|---|
| BaseUrl | Base URL for external HEAT (e.g. https://your-env.heatvr.io). Required when not in-cluster. Do not include a trailing slash. |
| InCluster | When true, use in-cluster defaults (V2 API at http://heat-v2-api:5000/api/v2, Auth at http://heat-auth). |
| Token | Long-lived token (e.g. offline token). Use this or Username+Password. |
| Username | Username for password auth. |
| Password | Password for password auth. |
| RememberMe | When using username/password, request a refresh token (default true). |
| AutoConnect | When 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:
- Instantiates a HEAT client from environment variables.
- Connects and enumerates session templates and (optionally) platform config.
- Lists projects and picks one (or uses
HEAT_PROJECT_ID). - Creates a session with the desired (or default) template.
- Uploads a small text payload to the first node instance.
- 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.ExampleOptional:
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.
End-to-end workflow (upload data)
A typical flow to upload data to HEAT:
- Connect —
await client.ConnectAsync();(or rely on AutoConnect). - Discover — Optionally call
GetSessionTemplatesAsync(),GetPlatformConfigurationByPrefixAsync(prefix), andGetProjectsAsync()to choose a project and template. - Create session —
CreateSessionAsync(projectId, new CreateSessionRequest { ... }). UseSessionTemplateIdfrom a template or leave null for the project default. - Get node instance — From
session.NodeInstances, pick the node (e.g. the first input node) and use itsId. - Upload —
UploadNodeOutputAsync(nodeInstanceId, stream). - Verify —
GetNodeOutputsAsync(nodeInstanceId)to confirm the new output appears. - 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.
| Exception | When it is thrown | What to do |
|---|---|---|
| HeatClientConfigurationException | Invalid 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. |
| HeatConnectionException | Health 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. |
| HeatAuthenticationException | Auth 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. |
| HeatApiException | V2 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
| Symptom | Likely cause | Action |
|---|---|---|
| ”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 timeouts | App outside cluster cannot reach HEAT. | Use the public ingress URL; check firewall/DNS. |
| GetNodeOutputsAsync returns empty list | Node 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. |
Related documentation
- Client runtimes & SDKs — Overview of HEAT client libraries (including HEAT.Common).
- Sessions — How sessions and session templates work in HEAT.
- Session templates — Defining workflows and node graphs.
- Node instances — Runtime nodes in a session.
- Offline tokens — Long-lived tokens for unattended or device scenarios.
- External API V2 — Low-level V2 API (use HEAT.Common instead when building .NET upload clients).
Source code and example: runtimes/csharp/HEAT.Common and runtimes/csharp/HEAT.Common.Example in the HEAT repository.