HEAT v2 API Documentation
The v2 API is the versioned external interface of HEAT. It is the supported way for any system outside the platform to create sessions, send data, follow progress and collect results. This reference is for developers who integrate with it.
The version is part of every route. A published route or response shape is not changed within a version. New capability arrives as new routes or new fields. Where a section says a contract is still settling, treat that section as provisional until the note is removed.
This site is the living reference and moves with each release. Versioned interface control documents for the external interfaces are available on request. See Architecture and boundaries for how the external surface differs from the platform’s internal interfaces.
Authentication and authorization
Access to all API endpoints requires a valid opaque token.
This token must be included in the Authorization header of each request:
Authorization: Bearer <opaque_token>The token is mapped to the authenticated user’s role (e.g., Admin, Trainee, Instructor) and determines whether the user has access to a specific route.
If the token is missing, invalid, expired, or lacks required permissions, the request will be rejected with a 401 Unauthorized response.
To Obtain a Token
A client must authenticate against HEAT Auth, our dedicated authentication service.
During this process, users provide their credentials (username and password), and upon successful authentication, the service issues an opaque token.
Example 401 Response
{
"status": "error",
"message": "Unauthorized. Please log in to access user data."
}Admin: Offline Tokens
Administrator-only routes for managing long-lived offline tokens used by capture devices. Offline tokens cannot call these routes.
Base path: /api/v2/admin/offline-tokens
Create offline token
Endpoint: POST /api/v2/admin/offline-tokens
Authorization: Administrator access token (Bearer). Offline tokens rejected with 403.
Request body: None
Success (200 OK):
{
"token": "dGhpc2lzYW5vZmZsaW5ldG9rZW4=",
"expiresAt": "2026-10-07T09:30:00Z"
}The token value is shown only at creation. Store it securely on the capture device immediately.
List offline tokens
Endpoint: GET /api/v2/admin/offline-tokens
Authorization: Administrator access token (Bearer).
Success (200 OK):
[
{
"secretId": "550e8400-e29b-41d4-a716-446655440000",
"userId": "660e8400-e29b-41d4-a716-446655440001",
"username": "admin",
"expiresAt": "2026-10-07T12:00:00Z",
"lastUsedAt": "2025-10-07T09:30:00Z",
"revoked": false,
"tokenHint": "abcd"
}
]Revoke offline token
Endpoint: DELETE /api/v2/admin/offline-tokens/{secretId}
Authorization: Administrator access token (Bearer).
Success (204 No Content)
Not found (404): No offline token with the given secretId.
See also Offline Tokens (HEAT Auth) for heat-auth direct endpoints and capture-device usage.
1. Health Check
Endpoint: GET /health_check
Description: Checks if the service is up and running. Useful for monitoring and automated service checks.
Success (200 OK)
{
"status": "ok"
}Failure (503 Service Unavailable)
{
"status": "error",
"message": "Service unavailable. Please try again later."
}2. Retrieve Session Templates
Endpoint: GET /api/v2/session/session_templates
Description: Returns all the session templates available within a specific Heat instance.
Authorization: Administrator, Instructor, Trainee or Developer access token (Bearer). Offline tokens are accepted.
Success Response
[
{
"id": 1,
"name": "Default Template",
"description": "Base template for sessions",
"configuration": "{}",
"nodes": []
},
{
"id": 2,
"name": "Advanced Template",
"description": "Includes extended configurations",
"configuration": "{}",
"nodes": []
}
]Fetch one session template
Endpoint: GET /api/v2/session/session_templates/{sessionTemplateId}
Description: Returns one session template with its nodes. Each node carries the name and type of its node template and the names of its parent nodes. Use it to find the input nodes of a template before you create a session from it.
Authorization: Administrator, Instructor, Trainee or Developer access token (Bearer). Offline tokens are accepted.
curl "https://your-host/api/v2/session/session_templates/12" \
-H "Authorization: Bearer $TOKEN"Success (200 OK)
{
"id": 12,
"name": "csv-dashboard",
"description": "CSV to dashboard",
"configuration": "{}",
"nodes": [
{
"id": 30,
"name": "csv-input",
"nodeTemplateId": 1,
"nodeTemplateName": "input-node",
"nodeTemplateType": "Input",
"defaultConfiguration": "{\"dataSourceName\":\"HEAT Managed Object Store\"}",
"parentNames": []
},
{
"id": 31,
"name": "dashboard",
"nodeTemplateId": 6,
"nodeTemplateName": "dashboard-v2",
"nodeTemplateType": "Aggregation",
"defaultConfiguration": "{}",
"parentNames": ["csv-input"]
}
]
}nodeTemplateType is Input, Transform, Processing or Aggregation. nodeTemplateName and nodeTemplateType are null when the platform could not read its node template catalog. configuration and defaultConfiguration are JSON documents held in a string, as stored.
The node id values here belong to the template. A session gets its own node instance ids when it is created. Read them from node instance progress.
Failure (404 Not Found)
{ "status": "error", "message": "Session template not found." }3. Get Logged-in User Data
Endpoint: GET /user_data
Description: Retrieves the profile details of the currently logged-in user. This includes personal information, account status, and role information.
Response Example
{
"UserId": "f3c1e7a2-9d4b-4c0a-b5a7-6f3c73b47f29",
"Username": "jdoe",
"FirstName": "John",
"LastName": "Doe",
"Title": "Product Manager",
"Company": "Heat Solutions",
"Email": "jdoe@heatsolutions.com",
"Role": "Admin",
"CreatedAt": "2024-09-15T10:30:00Z",
"UpdatedAt": "2025-01-10T15:45:00Z",
"ForceChangePasswordNextSignin": false,
"IsRegistrationComplete": true,
"HasAcceptedDataPrivacy": true,
"IsActive": true
}4. Create Project
Endpoint: POST /projects
Description: Creates a new project within a Heat instance.
Request Payload
{
"Title": "Sample Project",
"Description": "Project for testing",
"Icon": "project_icon.png",
"OwnerId": 123,
"DefaultSessionTemplateId": 1
}Success (201 Created)
{
"id": 101,
"hexId": "a1b2c3d4",
"title": "Simulation Project",
"description": "A test project for distributed environments.",
"icon": "https://example.com/icons/project.png",
"ownerId": 501,
"owner": {
"id": 501,
"email": "owner@example.com",
"type": "Admin",
"isActive": true,
"createdAt": "2025-09-15T10:30:00Z",
"externalB2CId": "ext-b2c-12345",
"externalHEATId": "heat-98765",
"sessionUsers": [],
"sessionDimensions": []
},
"defaultSessionTemplateId": 7001,
"defaultSessionTemplate": {},
"createdAt": "2025-10-02T12:00:00Z"
}Failure (400 Bad Request)
{
"status": "error",
"message": "Invalid project data."
}5. Update Project
Endpoint: PUT /projects/{id}
Description: Updates a specific project using the Id of the project.
Request Payload
{
"Title": "Sample Project",
"Description": "Project for testing",
"Icon": "project_icon.png",
"OwnerId": 123,
"DefaultSessionTemplateId": 1
}Success (200 OK)
(Same as Create Project Response)
Failure Responses
{ "status": "error", "message": "Project not found." }
{ "status": "error", "message": "Invalid project data." }6. Get Projects
Endpoint: GET /projects
Description: Retrieves all projects within a Heat instance.
Response Example
[
{
"id": 45,
"hexId": "a1b2c3d4",
"title": "Sample Project",
"description": "A test project for distributed environments.",
"icon": "https://example.com/icons/project.png",
"ownerId": 123,
"owner": {
"id": 501,
"email": "owner@example.com",
"type": "Admin",
"isActive": true,
"createdAt": "2025-09-15T10:30:00Z",
"externalB2CId": "ext-b2c-12345",
"externalHEATId": "heat-98765",
"sessionUsers": [],
"sessionDimensions": []
},
"defaultSessionTemplateId": 1,
"defaultSessionTemplate": {},
"createdAt": "2025-10-02T12:00:00Z"
}
]7. Get a Specific Project
Endpoint: GET /projects/{id}
Description: Retrieves a specific project using the project Id.
Response Example
(Same as Get Projects Response)
Failure (404 Not Found)
{
"status": "error",
"message": "Project not found"
}8. Create Session
Endpoint: POST /session
Description: Creates a session associated with a given project and template.
Request Payload
{
"Name": "John Doe",
"Email": "johndoe@example.com",
"SimulationName": "Heat Simulation Run 1",
"SessionTemplateId": 101,
"ProjectId": 1,
"Metadata": {}
}Success (201 Created)
{
"id": "d3c5d9a2-7f29-4e7a-9f64-9b1d75c9e321",
"name": "Example Session",
"email": "example.user@email.com",
"projectId": 101,
"project": {
"id": 101,
"hexId": "a1b2c3d4",
"title": "AI Research Project",
"description": "Project focused on building simulation environments for AI models.",
"icon": "https://example.com/icons/project-icon.png",
"ownerId": "f42e79ab-221b-4c18-b9a3-7a53ac2a09c9",
"owner": {
"id": "f42e79ab-221b-4c18-b9a3-7a53ac2a09c9",
"name": "Alice Johnson",
"email": "alice.johnson@example.com"
},
"defaultSessionTemplateId": 5001,
"defaultSessionTemplate": {
"id": 5001,
"name": "Default AI Simulation Template",
"description": "Template for AI training simulations",
"configuration": {
"maxNodes": 10,
"cpuLimit": "4 cores",
"memoryLimit": "16GB"
},
"nodes": []
},
"createdAt": "2025-10-02T12:00:00Z"
},
"sessionTemplateId": 5001,
"sessionTemplate": {
"id": 5001,
"name": "Default AI Simulation Template",
"description": "Template for AI training simulations",
"configuration": {
"maxNodes": 10,
"cpuLimit": "4 cores",
"memoryLimit": "16GB"
},
"nodes": [
{
"id": 1,
"type": "InputNode",
"label": "JsonInput Node"
},
{
"id": 2,
"type": "Preprocessor",
"label": "Preprocessing Node"
}
]
},
"simulationName": null,
"scenarios": [],
"dimensions": [],
"logoUrl": "https://example.com/logos/session-logo.png",
"userId": "d3c5d9a2-7f29-4e7a-9f64-9b1d75c9e321",
"metadata": null,
"sessionUsers": [],
"createdAt": "2025-10-02T12:30:00Z"
}Failure Response (400 Bad Request)
{
"status": "error",
"message": "Invalid session request. ProjectId and TemplateId are required."
}9. Update Session
Endpoint: PATCH /sessions/{id}
Description: Update an existing session by its ID. Supports partial updates (only provided fields are changed).
Auth: Bearer token required.
Request (JSON)
Only include fields you want to change.
{
"name": "John Doe",
"email": "johndoe@example.com",
"simulation_name": "Heat Simulation Run 1",
"session_template_id": 101,
"project_id": 1,
"metadata": {}
}emailvalidated if provided.metadatamust be valid JSON (sent upstream as a JSON string).
Responses
200 OK
Returns the full updated session, including node instances and default ingestion node id.
{
"id": "6d551cfb-6f3f-41cc-a48a-b6eb1f7f3abb",
"name": "Training Session",
"email": "trainer@example.com",
"simulation_name": "Network Optimization",
"project_id": 101,
"project": {
"hex_id": "a1b2c3d4",
"title": "AI Research Project",
"description": "Project for testing and simulating AI workflows.",
"icon": "https://example.com/icons/project.png",
"owner_id": 501,
"default_session_template_id": 7001,
"created_at": "2025-10-02T12:00:00Z"
},
"session_template_id": 7001,
"session_template": {
"name": "Default Template",
"description": "Template used by default",
"configuration": "{}"
},
"metadata": {},
"logo_url": "https://example.com/logos/session.png",
"node_instances": [],
"default_ingestion_node_id": null,
"session_users": [],
"dimensions": [],
"scenarios": []
}400 Bad Request
{ "status": "error", "message": "Invalid session data" }401 Unauthorized / 403 Forbidden
{ "status": "error", "message": "Unauthorized" }{ "status": "error", "message": "Forbidden" }404 Not Found
{ "status": "error", "message": "Session not found" }Notes
- Partial updates: omit fields you don’t want to change.
- Snake case: paths and fields use snake_case (
/sessions,session_template_id, etc.). - Project & Template IDs: must reference valid records.
- Controller enriches the response with
node_instances(same as creation flow).
Example
curl -X PATCH "https://your-host/api/v2/sessions/6d551cfb-6f3f-41cc-a48a-b6eb1f7f3abb" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"name":"John Doe","project_id":1,"metadata":{"a":1}}'Shared sessions
A shared session lets several capture machines in one exercise feed the same Session. The first caller creates the session. Later callers with the same name, simulation name, machine group, session template and window length join it, as long as their time window overlaps the session’s window.
Create or join a shared session
Endpoint: POST /api/v2/session/shared_session?projectId={projectId}
Authorization: Administrator, Instructor, Trainee or Developer access token (Bearer). Offline tokens are accepted.
{
"name": "Exercise Alpha",
"simulationName": "Exercise-A",
"email": "jdoe@example.com",
"sessionTemplateId": 12,
"startTimeUtc": "2026-09-17T09:00:00Z",
"machineGroup": 4,
"timeWindowSeconds": 300
}| Field | Description |
|---|---|
name, simulationName | Required. The session name and the simulation it belongs to. |
email | Required. The address of the participant, normally the person signed in on the capture machine. See Participant identity below. |
sessionTemplateId | Required. The session template to create the session from. |
startTimeUtc | Required. When this participant’s capture starts. |
machineGroup | Required. Machines in one exercise share a group number. |
timeWindowSeconds | Optional, default 300. The length of the join window. |
How matching works
The request joins the newest open session for which all of the following hold:
name,simulationName,sessionTemplateId,machineGroupandtimeWindowSecondsmatch exactly. A different revision of a session template has a differentsessionTemplateId, so all clients must point at the same revision.- The session has not been closed (see Close a shared session).
- The request window, from
startTimeUtctostartTimeUtcplustimeWindowSeconds, overlaps the session window stored in its metadata.
The overlap rule allows for clock skew between capture machines. startTimeUtc comes from each machine’s own clock, so the first machine to arrive may report a later start time than a machine with an accurate clock that arrives a moment afterwards. Either order joins, as long as the two windows overlap. A request whose whole window falls before or after the session’s window starts a new session. Keep machine clocks synchronised anyway, because the first request sets the startTimeUtc stored on the session.
Session metadata records status, startTimeUtc, timeWindowSeconds, machineGroup, openedBy and participants. Joining adds the participant to participants, as an identifier (see Participant identity).
Success: 201 Created when the call created the session, 200 OK when it joined an existing one. The body is the session, with node_instances for uploading.
Failure: 400 for an invalid body. 404 when the project or the session template does not exist. 503 Service Unavailable with Retry-After: 1 when another request for the same session is being processed. Retry it.
Close a shared session
Endpoint: POST /api/v2/session/{id}/close
{
"email": "jdoe@example.com",
"reason": "Exercise complete",
"closeAt": "2026-09-17T10:30:00Z"
}Only a participant can close the session. Closing twice is safe and returns the first result.
Success (200 OK)
{
"id": "6d551cfb-6f3f-41cc-a48a-b6eb1f7f3abb",
"status": "closed",
"participantCount": 2,
"closedAt": "2026-09-17T10:30:00Z",
"closedBy": "jdoe@example.com"
}Failure: 403 when the caller is not a participant. 404 when the session does not exist.
Participant identity
The request and response shapes carry an email address. HEAT does not store it.
Before the session is written, the API replaces the address with the participant’s HEAT Auth identifier. The platform stores that identifier in the session’s email field and in the openedBy, closedBy and participants keys of the session metadata. An address that HEAT Auth does not resolve for the caller is stored as a one-way key that starts anon:. The same address always gives the same key, so joining and closing still match.
On the way out, the API joins each stored identifier back to an address for that one response, using the caller’s own token. HEAT Auth decides which people the caller may see. A person the caller may not see stays as an identifier.
Session metadata sent on the update route is treated the same way, so reading a session and writing its metadata back does not store an address. The session list returns identifiers and never joins them to addresses. See Architecture and boundaries for the model.
Session list
Endpoint: GET /api/v2/session
Description: Returns one page of sessions, newest first by default. Use it to find sessions your software did not create itself, and to read session metadata across sessions.
Authorization: Administrator, Instructor or Developer access token (Bearer). Offline tokens are accepted. A Trainee token receives 403.
Query parameters
| Parameter | Description |
|---|---|
projectId | Optional. Only sessions in this project. |
sessionTemplateId | Optional. Only sessions created from this session template. |
search | Optional. Matches part of the session name, the simulation name or the session id. |
sortBy | date (default) or name |
sortOrder | desc (default) or asc |
page | Page number, from 1. Default 1. |
pageSize | Rows for each page, 1 to 100. Default 25. |
curl "https://your-host/api/v2/session?projectId=7&sessionTemplateId=12&page=1&pageSize=50" \
-H "Authorization: Bearer $TOKEN"Success (200 OK)
{
"sessions": [
{
"id": "6d551cfb-6f3f-41cc-a48a-b6eb1f7f3abb",
"name": "Gunnery run 4",
"simulationName": "Exercise-A",
"projectId": 7,
"projectTitle": "Range",
"sessionTemplateId": 12,
"sessionTemplateName": "fn-shots-basic-r021",
"createdAt": "2026-09-01T10:00:00Z",
"metadata": { "reporting": { "kpis": { "detections": 12 } } }
}
],
"page": 1,
"pageSize": 50,
"totalCount": 1,
"totalPages": 1
}metadata is the session metadata as a JSON object, or null when the session has none.
A row is a summary. It carries no email address, no owner and no session users. Fetch the session by id for the full record, its dimensions and its scenarios.
Sessions that back system dashboards are never listed. Find those through System dashboards list.
Failure (400 Bad Request) for a page below 1, a page size outside 1 to 100, or an unknown sortBy or sortOrder.
{ "status": "error", "message": "pageSize must be between 1 and 100." }Node instance progress
Endpoint: GET /api/v2/session/{id}/node-instances
Description: Returns the processing state of every node instance in a session, with a count for each state. The response carries no configuration and no output lists, so it is cheap to poll after an upload.
Authorization: Administrator, Instructor, Trainee or Developer access token (Bearer). Offline tokens are accepted.
curl "https://your-host/api/v2/session/6d551cfb-6f3f-41cc-a48a-b6eb1f7f3abb/node-instances" \
-H "Authorization: Bearer $TOKEN"Success (200 OK)
{
"sessionId": "6d551cfb-6f3f-41cc-a48a-b6eb1f7f3abb",
"nodeInstances": [
{
"id": 21,
"name": "csv-input",
"nodeTemplateId": 1,
"nodeTemplateName": "input-node",
"state": "Succeeded",
"stateCode": 3,
"statusDetails": "Processing succeeded; node is ready for more work.",
"lastProcessed": "2026-09-02T08:30:00Z",
"currentOutputId": 900,
"parentIds": []
},
{
"id": 22,
"name": "dashboard",
"nodeTemplateId": 6,
"nodeTemplateName": "dashboard-v2",
"state": "Processing",
"stateCode": 1,
"statusDetails": "",
"lastProcessed": null,
"currentOutputId": null,
"parentIds": [21]
}
],
"summary": {
"total": 2,
"counts": { "Succeeded": 1, "Processing": 1 },
"allSucceeded": false,
"anyFailed": false
}
}state | stateCode | Meaning |
|---|---|---|
NeverBegan | 0 | The node has not run yet. |
Processing | 1 | A runner is working on the node. |
Failed | 2 | The last run failed. statusDetails says why. |
Succeeded | 3 | The last run finished and stored an output. |
Suspended | 4 | Processing of the node is suspended. |
Pending | 5 | A task is queued and waits for a runner. |
Unknown | other | A state this version of the API does not name. |
currentOutputId is the newest output of the node. Pass it to the read route under Node instance uploads and outputs.
summary.allSucceeded is true when the session has nodes and every one is Succeeded. A template with a branch that never receives data keeps that branch at NeverBegan, so check the nodes you care about by name instead of waiting for allSucceeded.
Failure (404 Not Found)
{ "status": "error", "message": "Session not found." }Node instance uploads and outputs
These routes are how data enters a session and how stored results are read back. A node instance is one node of a running session. Its id comes from the node_instances list on the session responses above. Uploading to an input node creates a node output on it, and the rest of the session template runs from there.
Base path: /api/v2/node-instance
Authorization: Administrator, Instructor, Trainee or Developer access token (Bearer). Offline tokens are accepted on all three routes.
Upload data to a node instance
Endpoint: POST /api/v2/node-instance/{nodeInstanceId}/outputs/data
Request body: The raw bytes of the payload, sent as the request body. There is no multipart wrapper and no JSON envelope. Set Content-Type to match the payload, for example text/csv or application/octet-stream.
The node instance’s configuration must carry a dataSourceName that matches a registered S3-compatible data source. Session templates set this on the input node, for example "dataSourceName": "HEAT Managed Object Store".
curl -X POST "https://your-host/api/v2/node-instance/4711/outputs/data" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: text/csv" \
--data-binary @sample.csvSuccess (201 Created) with the new node output. 200 OK carries the same shape when an existing output was updated.
{
"id": 90210,
"nodeInstanceId": 4711,
"configuration": "{}",
"createdAt": "2026-03-04T10:15:30Z",
"dataSourceId": 1,
"dataPath": "4711/3f9c2a1e5d0b4c7a9e8f6d5c4b3a2f10",
"outputIdentifier": "3f9c2a1e5d0b4c7a9e8f6d5c4b3a2f10"
}nodeInstance and dataSource may also be present as nested objects. On this route the node is marked as processed at the same time, so the next node in the template becomes eligible to run.
Accepted (202 Accepted) when the project has queued ingestion enabled. The payload is placed on the ingestion queue and the node output appears once the queue is drained. Do not treat the node as complete on a 202.
{
"status": "queued",
"projectId": 12,
"sessionId": "6d551cfb-6f3f-41cc-a48a-b6eb1f7f3abb",
"nodeInstanceId": 4711,
"stream": "HEAT_INGEST",
"subject": "heat.ingest.12"
}The stream and subject values are informational. They name the internal queue and the per-project subject the payload was placed on.
Failure responses
| Status | Message | Cause |
|---|---|---|
| 400 | Invalid node configuration JSON. | The node instance configuration could not be parsed |
| 400 | Missing DataSourceName in node configuration. | The node has no dataSourceName. Set it on the input node in the template. |
| 404 | Node instance not found. | Wrong nodeInstanceId |
| 404 | Data source '<name>' not found. | The named data source is not registered, or is not S3-compatible |
| 500 | Failed to create node output. | Storage or upstream error |
Failures use the common shape { "status": "error", "message": "..." }.
List outputs of a node instance
Endpoint: GET /api/v2/node-instance/{nodeInstanceId}/outputs
Description: Returns every node output on the node instance, ordered by creation time. For an input node these are the uploads it received. For a processing node they accumulate over time, because reprocessing appends a new output rather than replacing one.
Success (200 OK)
[
{
"id": 90210,
"nodeInstanceId": 4711,
"configuration": "{}",
"createdAt": "2026-03-04T10:15:30Z",
"dataSourceId": 1,
"dataPath": "4711/3f9c2a1e5d0b4c7a9e8f6d5c4b3a2f10",
"outputIdentifier": "3f9c2a1e5d0b4c7a9e8f6d5c4b3a2f10"
}
]Failure (404 Not Found)
{ "status": "error", "message": "Node instance or outputs not found." }Read the bytes of a node output
Endpoint: GET /api/v2/node-instance/outputs/{nodeOutputId}/data
Description: Streams the stored bytes of one node output as they are, with the content type they were stored under. There is no JSON probing and no buffering, so binary artefacts of any size are served correctly. When the output is a byte range inside a bundled object, the range is sliced on the server and the id alone addresses it.
curl -L "https://your-host/api/v2/node-instance/outputs/90210/data" \
-H "Authorization: Bearer $TOKEN" \
-o output.binSuccess (200 OK) with the raw bytes. Content-Type is the stored type, or application/octet-stream when none was recorded.
Failure (404 Not Found)
{ "status": "error", "message": "Node output not found." }Other upstream failures return the upstream status with { "status": "error", "message": "..." }.
Read the newest output of a node instance
Endpoint: GET /api/v2/node-instance/{nodeInstanceId}/outputs/latest/data
Description: Returns the bytes of the newest output of a node instance in one call. Use it to collect the current result of a node that runs repeatedly, such as the report node of a scheduled analysis.
Authorization: Administrator, Instructor, Trainee or Developer access token (Bearer). Offline tokens are accepted.
curl -L "https://your-host/api/v2/node-instance/22/outputs/latest/data" \
-H "Authorization: Bearer $TOKEN" \
-o latest.jsonSuccess (200 OK) with the raw bytes. Content-Type is application/json when the output is a JSON document of 4 MB or less, and application/octet-stream otherwise.
Failure (404 Not Found) when the node instance does not exist or has no output yet.
{ "status": "error", "message": "Node instance not found, or it has no output yet." }Reprocess a node instance
Endpoint: POST /api/v2/node-instance/{nodeInstanceId}/reprocess
Description: Queues a new task for the node instance, so its runner processes it again. The nodes below it follow as usual. Use it to run an analysis template on demand. The request has no body.
Authorization: Administrator or Developer access token (Bearer). Offline tokens are refused with 403, because the call starts work on the cluster.
curl -X POST "https://your-host/api/v2/node-instance/22/reprocess" \
-H "Authorization: Bearer $TOKEN"Success (202 Accepted)
{
"nodeInstanceId": 22,
"status": "queued",
"message": "A new task was queued for this node instance."
}The task runs later. Poll node instance progress to see it finish.
Failure: 404 when the node instance does not exist. 400 when the platform refuses, for example because the session is frozen. Both use { "status": "error", "message": "..." }.
Runners
These routes are for runners built with the HEAT Runner SDK: software that executes node templates for HEAT. They are not for integrations that create sessions or upload data. The SDK uses them for you. Call them directly only when you write a runner in a language the SDK does not cover yet. The machine-readable contract is the OpenAPI file runner-api.v1.yaml published with the SDK.
Runner tokens
Runner routes accept two kinds of credential:
- A runner token, which starts with
hrt_. It is scoped to one or more runner types, and through them to the node templates those runner types serve. Runners the platform deploys always use one, and it is the right choice for any runner you host and leave running. Runner tokens are refused on every other route. - For development, the access token of your HEAT account, when the account has the Administrator or Developer role. Sign in with HEAT Auth as for any other v2 call. An account can claim tasks only for runner types whose
runtimeisexternal, so a developer’s machine never takes work meant for runners the platform deploys. It can use the read-only debug routes for any node.
Offline tokens, and accounts with other roles, are refused on runner routes.
A runner token carries scopes:
| Scope | Allows |
|---|---|
tasks | Claiming tasks, reading their inputs, writing outputs and reporting the outcome. |
debug:read | Read-only views of a node’s context and inputs, for capturing test fixtures. Nothing is claimed or changed. |
Runners the platform deploys for you receive a token automatically. For a runner you host yourself, an Administrator or Developer issues one:
Endpoint: POST /api/v2/admin/runner-tokens
Authorization: Administrator or Developer access token (Bearer). Offline tokens are refused.
{ "runnerTypes": ["hello-runner"], "scopes": ["tasks", "debug:read"], "ttlHours": 168, "label": "laptop" }Success (201 Created) returns the token once in token, with its id, expiresAt and scopes. It cannot be read again. GET /api/v2/admin/runner-tokens lists tokens without their values, and DELETE /api/v2/admin/runner-tokens/{id} revokes one. A revoked token stops working within 30 seconds. Developers see and revoke the tokens they issued. Administrators see all of them. Developer tokens last at most 30 days by default.
Publishing a runner manifest
Endpoint: POST /api/v2/admin/runner-manifests
Authorization: Administrator or Developer access token (Bearer). Offline tokens are refused.
The body is the runner’s manifest.heat. The platform checks it before registering anything:
- every runner type declares
runtimeassdk(deployed by the platform) orexternal(hosted by you), - the feature’s
ownershipTypeisThirdParty, - node template and runner type names use lowercase latin letters, digits and single hyphens, and start with a letter,
- configuration schemas are inline, with no
$ref, - no node template or runner type registered by another feature is replaced.
Success (200 OK) with { "published": true, "runnerTypes": [...], "templates": [...], "errors": [], "warnings": [...] }. Failure (400) has the same shape with published: false and every issue listed with its JSON path. POST /api/v2/runner/manifests/validate runs the same checks with a runner token and changes nothing.
Runner routes
All paths start with /api/v2/runner. Errors use { "status": "error", "code": "...", "message": "..." }, and code is stable.
| Route | Purpose |
|---|---|
GET /whoami | The token’s runner types, scopes, expiry, claimable templates and the server limits. |
POST /tasks/claim | Claim the next task. Body { "instanceId": "host-1", "templates": [...], "waitSeconds": 20 }. 200 with the task, or 204 when none arrived within the wait. |
PUT /tasks/{taskId}/heartbeat | Keep the task alive and report progress (phase, message, optional progress). |
GET /tasks/{taskId}/inputs | Page through inputs. mode is since-watermark (default), latest or all. |
GET /tasks/{taskId}/inputs/{outputId}/data | Stream one input. Honours Accept-Encoding: gzip and, for uncompressed inputs, Range. |
GET /tasks/{taskId}/state | The node’s previous output and incremental state. |
POST /tasks/{taskId}/outputs | Write an output of up to 16 MB in one request. |
POST /tasks/{taskId}/outputs/uploads | Start a multi-part upload for a larger output, then PUT .../parts/{n} and POST .../complete. DELETE .../uploads/{handle} aborts. |
PUT /tasks/{taskId}/watermark | Advance the watermark without writing an output. |
PUT /tasks/{taskId}/complete | Finish with outcome completed, no-output or suspended. |
PUT /tasks/{taskId}/fail | Report a failure with a reason and a category (error, validation, memory, timeout). |
PUT /tasks/{taskId}/release | Give an unfinished task back, for example on shutdown. |
GET /debug/nodes/{nodeInstanceId}/context | Read-only: what a task for this node would receive. Needs debug:read. |
GET /debug/nodes/{nodeInstanceId}/inputs[/{outputId}/data] | Read-only input listing and download. Needs debug:read. |
Every /tasks/{taskId}/... call must send X-Heat-Runner-Instance with the instanceId the task was claimed with. A task belongs to the token and instance that claimed it, and other callers get 404. Claiming again with the same instanceId gives up any task that instance still holds, so run one task at a time per instance id.
Outputs
Send the output bytes as the body with:
X-Heat-Output-Identifier: your identifier for the output, unique per node (letters, digits,.,_,-, up to 100 characters). Sending the same identifier again returns the existing output with200instead of201, so retries are safe.Content-Type, andContent-Encoding: gzipwhen you compressed the body.X-Heat-Output-Metadata(optional): a JSON object of up to 4 KB stored with the output and returned with it. Keys starting with$are reserved.X-Heat-Watermark(optional): advance the watermark to this input id together with the output.
Incremental processing
A node keeps a watermark: the id of the last parent output it fully consumed. since-watermark lists the inputs newer than the watermark, oldest first, capped per task (see limits.incremental in whoami). When a task stops at the cap, complete it with hasMore: true and the platform schedules a follow-up task for the rest. The watermark only moves forward, and only to the id of an input of the task. A lower position returns 409 with watermark_regression, and an id that is not an input returns 409 with watermark_unknown_input. Every task and debug context shows the watermark, the number of pending inputs and the caps under incremental, so a runner can see exactly what it is about to consume.
10. Add Scenario Instance to a Session
Endpoint: POST /{sessionId}/scenario_instances
Description: Creates a scenario using a session and returns the corresponding scenarioId.
Request Payload
{
"simulationId": "string",
"scenarioName": "string",
"scenarioInstanceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"from": "2025-10-02T10:39:24.760Z",
"to": "2025-10-02T10:39:24.760Z"
}Success (200 OK)
{
"status": true,
"message": "Scenario instance retrieved successfully",
"template": {
"simulationId": 67890,
"sessionId": 54321,
"scenarioName": "Test Scenario A",
"scenarioInstanceId": "scn-001",
"from": "2025-10-01T08:00:00Z",
"to": "2025-10-01T10:00:00Z",
"dimensions": [],
"createdAt": "2025-10-02T12:00:00Z"
}
}Failure
{ "status": "error", "message": "SessionId not found." }
{ "status": "error", "message": "Invalid request. Scenarion name is required." }11. Get Configurations
Endpoint: GET /config/all
Description: Retrieves all platform configurations.
Success (200 OK)
{
"public_api_v1": {
"AzureAdB2CSettings": {},
"BlobStorageSettings": {},
"SendGridSettings": {},
"ContactSupportSettings": {},
"ConnectionStrings": {}
}
}12. Create Configuration
Endpoint: POST /config
Description: Creates a new platform configuration.
Request Payload Example
{
"name": "string",
"valueType": 0,
"value": "string",
"description": "string"
}Success (201 Created)
{
"id": 1001,
"name": "MaxTrainingEpochs",
"valueType": 2,
"value": "50",
"description": "Maximum number of epochs allowed for training models in this project.",
"createdAt": "2025-10-01T10:00:00Z",
"updatedAt": "2025-10-02T12:30:00Z"
}Failure (400 Bad Request)
{
"status": "error",
"message": "Invalid configuration name"
}Failure (409 Conflict)
{
"status": "error",
"message": "Configuration with the same name already exists."
}13. Update Configuration
Endpoint: PUT /config/{name}
Description: Updates an existing platform configuration by name.
Request Payload Example
{
"name": "string",
"valueType": 0,
"value": "string",
"description": "string"
}Success (200 OK)
{
"id": 1001,
"name": "MaxTrainingEpochs",
"valueType": 2,
"value": "50",
"description": "Maximum number of epochs allowed for training models in this project.",
"createdAt": "2025-10-01T10:00:00Z",
"updatedAt": "2025-10-02T12:30:00Z"
}Failure
{
"status": "error",
"message": "Invalid configuration name."
}
{
"status": "error",
"message": "Configuration not found"
}14. Dashboard static navigation
Endpoint: GET /api/v2/dashboard/statics/nav (the version segment matches your deployment, for example api/v2.0 where the v2 host substitutes the default API version in the URL.)
Description: Returns sidebar-ready links for platform static definitions that use a v2 Next session dimension. The v2 API aggregates the platform’s static definitions and their backing session dimensions. Dashboard SPAs should use this route instead of calling core statics directly from the browser.
Phase 1 scope: only global singleton statics (scopeKind 0) with at least one non-deleted instance and a Next DashboardVersion dimension are included. Per-project and per-user statics are omitted until instance selection is defined for the caller.
Success (200 OK)
{
"sections": [
{
"title": "System",
"code": "static-nav-system",
"order": 1,
"items": [
{
"title": "Resource Monitor",
"icon": "fluent:gauge-24-regular",
"url": "/statics/42",
"order": 1,
"code": "static-system-resource-monitor"
}
]
}
]
}url is an SPA path relative to the dashboard origin. The HEAT Next dashboard loads navigation from GET /api/v2/dashboard/navigation, which merges static items for Developer users server-side.
System dashboards list
Endpoint: GET /api/v2/dashboard/statics
Description: Lists the system dashboards (called statics in templates and presets) the caller may view, of every scope. Each entry names its session template, its refresh interval and the backing session of each instance.
Backing sessions are hidden from the session list. This route is how your software reaches the outputs of a session template that runs on a schedule.
Authorization: Administrator, Instructor, Trainee or Developer access token (Bearer). Offline tokens are accepted. Each static is filtered by its minimum view privilege, the same rule the navigation route applies.
Success (200 OK)
{
"statics": [
{
"key": "weekly-analysis",
"title": "Weekly analysis",
"description": "Trainee progress across the last seven days",
"icon": "fluent:gauge-24-regular",
"navigationSection": "Analysis",
"scope": "GlobalSingleton",
"sessionTemplateId": 40,
"sessionTemplateName": "weekly-analysis-r001",
"refreshIntervalMinutes": 1440,
"instances": [
{
"id": 5,
"sessionId": "0b1f6c1e-52a5-4f0a-9d0e-6a2f6b1f8f11",
"scopeProjectId": null,
"lastRefreshAttemptAtUtc": "2026-09-10T01:00:00Z",
"lastRefreshSuccessAtUtc": "2026-09-10T01:02:00Z"
}
]
}
]
}scope is GlobalSingleton, PerProject or PerUser. scopeProjectId is set on the instances of a PerProject static. The instances of a PerUser static do not say which user they belong to.
The platform can rotate a backing session, so sessionId changes over time. Read it from this route each time instead of storing it.
Pass sessionId to node instance progress to find the node instances, then read a node’s newest output.
Failure (502 Bad Gateway) when the platform could not list statics.
15. Dashboard session catalog
Endpoint: GET /api/v2/dashboard/dimensions
Description: Lists session dimensions for the Next dashboard sessions page (replaces legacy GET /v1/Session/sessions via the facade). Role-aware catalog scope:
| Role | Scope |
|---|---|
| Administrator, Instructor, Developer | All dimensions (Core admin=true; no requirement for dimension user membership) |
| Trainee | Dimensions where the caller is a dimension member |
Results are filtered by scenario instance date overlap with from/to. Dimensions with no scenario window set (From/To unset) are always included so static/system Next dimensions appear in the admin catalog. Date-only to values are treated as end-of-day on the server.
Query parameters: from, to (ISO dates), userId (filter by dimension member external id), pageNumber (default 1, page size 50), orderBy (ascending | descending).
Users: each item’s users[] (and the item name/email, taken from the first dimension user) carries the member’s external HEAT Auth GUID as id. name and email are resolved from HEAT Auth at request time using the caller’s token (display name is first and last name, else username, else email). Callers whose token cannot list HEAT Auth users (trainees) are resolved one user at a time through the per-user endpoint. A GUID HEAT Auth does not know, or an unreachable HEAT Auth, leaves name as the GUID and email empty. Core never stores names or emails.
Errors: If Core catalog loading fails, v2 returns an error status (for example 404 or 502) with { "status": "error", "message": "..." } instead of an empty 200 array.
16. Dashboard navigation
Endpoint: GET /api/v2/dashboard/navigation
Description: Returns role-filtered sidebar sections for the Next SPA (replaces POST /v1/sitemap). Developer responses include static dashboard links from GET /api/v2/dashboard/statics/nav.
17. Dashboard feature flags
Endpoint: GET /api/v2/dashboard/feature-flags?features=BackOfficeDeleteAccount,SymbolToggle
Description: Returns a map of feature name to enabled flag (replaces GET /v1/settings/feature-toggle). Values are derived from core platform configuration FeatureToggleSettings.FeatureToggles.
18. Dashboard reports
Endpoints:
GET /api/v2/dashboard/reports/catalogGET /api/v2/dashboard/reports/download
Description: Configurable session exports for the Next dashboard Reports page. Replaces legacy facade GET /v1/Report/download and customer-settings report metadata. Report content is driven by platform configuration keys under the report prefix (see the Reports API guide for the row model, column sources and filter rules).
Roles: Administrator, Instructor, and Developer.
Report catalog (200 OK)
GET /api/v2/dashboard/reports/catalog
{
"reports": [
{
"key": "session-list",
"name": "Session List",
"configurationKey": "sessionList"
}
],
"defaultReport": "session-list"
}Report download
GET /api/v2/dashboard/reports/download
Query parameters:
| Parameter | Description |
|---|---|
from, to | Optional ISO date range (from must be less than or equal to to when both are set) |
userId | Optional external HEAT user GUID (trainee filter) |
option or report | Report key from catalog (defaults to report.Settings.Default) |
format | xlsx (default) or csv |
Success: File download with Content-Disposition filename {prefix}_{ddMMyyyy_HH.mm}.{xlsx|csv} where prefix comes from report.Configurations.*.FileName (default SessionList).
Errors: 400 for an invalid date range. 502 when Core dimension catalog loading fails.
Report definition (authoring)
These endpoints expose structured report definitions for external tooling. They read and write the same report.* platform configuration keys used by export.
| Method | Path | Roles | Purpose |
|---|---|---|---|
GET | /api/v2/dashboard/reports/{key} | Admin, Instructor, Developer | Full definition (columns, filters, filename) |
POST | /api/v2/dashboard/reports | Admin, Developer | Create report |
PUT | /api/v2/dashboard/reports/{key} | Admin, Developer | Update report |
DELETE | /api/v2/dashboard/reports/{key} | Admin, Developer. Offline tokens are refused. | Delete report |
Create/update request body:
{
"key": "afv-all",
"name": "AFV All",
"configurationKey": "afvAll",
"fileName": "AFV_Report",
"dimensionNameFilter": "AFV Recognition",
"setAsDefault": true,
"replaceConfiguration": true,
"columns": [
{
"columnKey": "Date",
"name": "Date",
"expression": "from",
"type": "datetime",
"format": "yyyy-MM-dd",
"source": "scenarioInstance",
"order": 1
}
],
"filters": [
{
"filterKey": "ScenarioName",
"expression": "scenario_type",
"value": "AFV",
"source": "metadata"
}
]
}configurationKey is optional on create (derived from key, e.g. session-list → sessionList). replaceConfiguration defaults to true on update and removes column/filter keys omitted from the payload.
Column source values: dimension, metadata, scenarioInstance, user, dashboard. Assessment values come from metadata. The dashboard source is kept for Legacy reports and is not recommended. See Limitations.
Responses: 201 Created on create, 200 OK on update/get definition, 404 when the report key is unknown, 409 on duplicate create.
Delete: 204 No Content when the report and its report.Configurations.* keys are removed. 404 when the report key is unknown. 409 when the report is the only one left, because an environment always keeps one report. Deleting the default report makes the first remaining report the default.