Machines
Machines are the core resource in Haltless. Every alert, work order, and downtime record is attached to a machine, and so is every reading of a tag assigned to it. A machine lives inside a System, the connection the edge agent reads, and is made of the System tags assigned to it. This page also covers machine groups, tag mappings, and unit preferences. All endpoints require a bearer JWT.
Endpoints
| Method | Path | Description |
|---|---|---|
POST | /api/v1/machines | Create a machine |
GET | /api/v1/machines | List machines |
GET | /api/v1/machines/{machine_id} | Get a machine |
PATCH | /api/v1/machines/{machine_id} | Update a machine |
DELETE | /api/v1/machines/{machine_id} | Deactivate a machine |
POST | /api/v1/machines/import | Bulk import from CSV or XLSX |
POST | /api/v1/machine-groups | Create a group |
GET | /api/v1/machine-groups | List groups |
GET | /api/v1/machine-groups/tree | Group hierarchy as a tree |
GET | /api/v1/machine-groups/{group_id} | Get a group with members |
PUT | /api/v1/machine-groups/{group_id} | Update a group |
DELETE | /api/v1/machine-groups/{group_id} | Delete a group |
PUT | /api/v1/machine-groups/{group_id}/members | Replace a group's members |
POST | /api/v1/tag-mappings/auto-map | Suggest standardized names for raw tags |
PATCH | /api/v1/tag-mappings/{mapping_id} | Correct a tag mapping |
POST | /api/v1/tag-mappings/{mapping_id}/confirm | Confirm a tag mapping |
GET | /api/v1/units/supported | List supported units and conversions |
GET | /api/v1/settings/units | Get unit preferences |
PATCH | /api/v1/settings/units | Update unit preferences |
Create a machine
POST /api/v1/machines
Requires an operator or administrator role. A machine is created in one of two shapes:
- In an existing System: pass
system_id. The machine is read the way its System is, so it takes noprotocol. Adding a second machine to a System ends that System's auto-assignment: new tags then wait, unassigned, on the System's page. - On its own (the shape before Systems): omit
system_idand passprotocol. Haltless creates a System with the same identifier and routes every tag it sends to this machine, until another machine joins the System.409if a System already answers to that identifier.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Human-readable name (1–255 chars) |
machine_identifier | string | Yes | The machine's code, unique per company (1–255 chars) |
system_id | uuid | No | The System to create the machine in. 404 if it is not one of yours |
protocol | string | Without system_id | One of opc_ua, modbus_tcp, modbus_rtu, s7, ethernet_ip, melsec, fins, ads, bacnet_ip, mqtt, sparkplug_b, pi_web_api, csv, json; 422 if both are missing |
location | string | No | Physical location (up to 500 chars) |
{
"name": "Press 2",
"machine_identifier": "PRESS-002",
"system_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"location": "Building A, Line 3"
}
{
"name": "CNC Mill #1",
"machine_identifier": "CNC-001",
"protocol": "json",
"location": "Building A, Line 3"
}
Response: 201 Created
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"tenant_id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
"name": "CNC Mill #1",
"machine_identifier": "CNC-001",
"protocol": "json",
"status": "offline",
"connection": "offline",
"health": "healthy",
"last_seen_at": null,
"location": "Building A, Line 3",
"is_active": true,
"system_id": "550e8400-e29b-41d4-a716-446655440000",
"system_name": "CNC Mill #1",
"created_at": "2026-08-07T10:00:00Z",
"updated_at": "2026-08-07T10:00:00Z"
}
List machines
GET /api/v1/machines
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
page_size | integer | 20 | Items per page (max 100) |
group_id | uuid | Filter by machine group | |
system_id | uuid | Only the machines of this System | |
search | string | Match name or identifier (up to 200 chars) | |
status | string | Live status: one of healthy, warning, critical, offline | |
connection | string | online or offline | |
health | string | healthy, warning or critical, whether or not the machine is online | |
sort_by | string | name | One of name, machine_identifier, status, connection, health, location, updated_at, created_at. health sorts by severity |
sort_dir | string | asc | asc or desc |
Response: 200 OK
{
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"tenant_id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
"name": "CNC Mill #1",
"machine_identifier": "CNC-001",
"protocol": "json",
"status": "critical",
"connection": "online",
"health": "critical",
"last_seen_at": "2026-08-07T10:04:58Z",
"location": "Building A, Line 3",
"is_active": true,
"system_id": "550e8400-e29b-41d4-a716-446655440000",
"system_name": "CNC Mill #1",
"created_at": "2026-08-07T10:00:00Z",
"updated_at": "2026-08-07T10:00:00Z"
}
],
"pagination": {
"total": 1,
"page": 1,
"page_size": 20
}
}
Get a machine
GET /api/v1/machines/{machine_id}
Returns a single machine. Returns 404 if the machine does not exist or is not accessible to you.
Update a machine
PATCH /api/v1/machines/{machine_id}
Requires an operator or administrator role. Include only the fields you want to change.
| Field | Type | Description |
|---|---|---|
name | string | New name (1–255 chars) |
location | string | New location |
status | string | One of healthy, warning, critical, offline. Stored only: the status the API reports is always the live one (see Machine statuses) |
is_active | boolean | Active flag |
Deactivate a machine
DELETE /api/v1/machines/{machine_id}
Requires an operator or administrator role. Deactivates the machine (a soft delete); its tags return to its System, unassigned, so their new readings wait there until they are placed on another machine. Returns 204 No Content.
Bulk import
POST /api/v1/machines/import
Requires an operator or administrator role. Upload a CSV or XLSX file as multipart/form-data under the file field. Up to 500 rows per upload.
- Required columns:
name,machine_identifier - Optional columns:
system(the identifier of an existing System to create the machine in),protocol(required on rows without asystem: the machine gets a System of its own),location - Valid protocols:
opc_ua,modbus_tcp,modbus_rtu,s7,ethernet_ip,melsec,fins,ads,bacnet_ip,mqtt,sparkplug_b,pi_web_api,csv,json
Response: 200 OK
{
"total_rows": 48,
"success_count": 45,
"failed_count": 3,
"errors": [
{ "row": 12, "reason": "Duplicate machine_identifier: CNC-003" }
],
"created_machine_ids": ["550e8400-e29b-41d4-a716-446655440000"]
}
Machine groups
Organize machines into a hierarchy of sites, lines, and departments.
Create a group
POST /api/v1/machine-groups
Requires an operator or administrator role.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Group name (1–200 chars) |
group_type | string | No | One of site, line, department, custom (default custom) |
description | string | No | Free-text description |
color | string | No | Hex color, for example #3B82F6 |
parent_id | uuid | No | Parent group for nesting |
machine_ids | array of uuid | No | Machines to add on creation |
Response: 201 Created
{
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"name": "Building A",
"group_type": "site",
"description": "Main production facility",
"color": "#3B82F6",
"machine_count": 0,
"parent_id": null,
"created_at": "2026-08-07T10:00:00Z"
}
List groups and tree
GET /api/v1/machine-groups returns a flat list of groups. GET /api/v1/machine-groups/tree returns the same groups as a nested hierarchy, with each node carrying a children array.
Get, update, and delete a group
GET /api/v1/machine-groups/{group_id} returns a group including its machine_ids. PUT /api/v1/machine-groups/{group_id} and DELETE /api/v1/machine-groups/{group_id} require an operator or administrator role.
Replace group members
PUT /api/v1/machine-groups/{group_id}/members
Requires an operator or administrator role. Replaces the group's members with the supplied list.
{
"machine_ids": [
"550e8400-e29b-41d4-a716-446655440000",
"7c9e6679-7425-40de-944b-e07fc1f90ae7"
]
}
Tag mappings
Tag mappings translate a System's raw tag names into standardized metric names; the name is kept on the System's tag. To list the tags assigned to a machine, with their names, see Sensor Data.
Suggest mappings
POST /api/v1/tag-mappings/auto-map
Requires an operator or administrator role. Submits raw tags and returns standardized-name suggestions, which are also saved for review.
| Field | Type | Required | Description |
|---|---|---|---|
tags | array | Yes | 1–50 raw-tag entries |
tags[].raw_tag | string | Yes | Original tag name (1–500 chars) |
tags[].system_id | uuid | Yes | System the tag is sent on |
tags[].context | string | No | Optional hint about the tag |
{
"tags": [
{
"system_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"raw_tag": "ns2_i1001_temp_motor_1"
}
]
}
Response: 200 OK
{
"results": [
{
"raw_tag": "ns2_i1001_temp_motor_1",
"system_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"standardized_name": "motor_temperature",
"unit": "celsius",
"confidence_score": 0.94
}
],
"saved_count": 1
}
Correct or confirm a mapping
PATCH /api/v1/tag-mappings/{mapping_id} updates a mapping's standardized name and unit. POST /api/v1/tag-mappings/{mapping_id}/confirm marks a suggested mapping as confirmed without changes. Both require an operator or administrator role.
{
"standardized_name": "motor_temperature",
"unit": "celsius"
}
Units
GET /api/v1/units/supported returns supported unit types and the available conversion pairs. It does not require authentication.
{
"conversions": [
{ "from_unit": "celsius", "to_unit": "fahrenheit" }
],
"default_preferences": {
"temperature": "celsius",
"pressure": "psi"
}
}
GET /api/v1/settings/units returns your account's unit preferences. PATCH /api/v1/settings/units updates them and requires an operator or administrator role.
{
"unit_preferences": {
"temperature": "fahrenheit",
"pressure": "bar"
}
}
Machine statuses
The status is the machine's live state, worked out when it is asked for (the portal shows healthy as Online):
| Status | Description |
|---|---|
healthy | Reporting, with no open warning or critical alert |
warning | An open (unacknowledged, unresolved) warning alert |
critical | An open critical alert |
offline | Silent for longer than its own pace allows: five times its usual gap between readings, and at least 2 minutes. With too little history to tell its pace, 15 minutes. A machine that has never reported is offline |
Offline outranks the others: a machine that has stopped reporting is offline whatever its alerts say.
Each machine also carries the two parts its status is made of, which the portal's machine list shows as separate columns:
| Field | Values | Description |
|---|---|---|
connection | online, offline | Whether it is reporting, by the offline rule above |
health | healthy, warning, critical | What its open alerts say, whether or not it is online, so a machine that stopped mid-alarm reads offline and critical |
last_seen_at | timestamp or null | Its newest reading; null if it has never reported |