Skip to main content

Sensor Data

Push sensor readings into Haltless and query them back as latest values, time-series history, or aggregated summaries. Ingestion at `POST /api/v1/ingest` authenticates with an API key; every other endpoint on this page requires a bearer JWT.

Endpoints​

MethodPathDescription
POST/api/v1/ingestIngest a batch of readings (API key)
GET/api/v1/machines/{machine_id}/readings/latestLatest value per metric
GET/api/v1/machines/{machine_id}/readingsTime-series history
GET/api/v1/machines/{machine_id}/summaryAggregated stats per metric
GET/api/v1/machines/{machine_id}/tag-mappingsTag mappings for a machine
GET/api/v1/machines/{machine_id}/baselinesComputed baselines per metric
POST/api/v1/ingest-sourcesCreate a polling source
GET/api/v1/ingest-sourcesList polling sources
GET/api/v1/ingest-sources/{source_id}Get a polling source
PUT/api/v1/ingest-sources/{source_id}Update a polling source
DELETE/api/v1/ingest-sources/{source_id}Delete a polling source
POST/api/v1/ingest-sources/{source_id}/test-connectionTest a source connection

Ingest readings​

POST /api/v1/ingest

Authenticates with an API key sent in the X-API-Key header, not a bearer JWT. Send up to 1,000 readings per request. See the API Keys guide for issuing keys and the Direct Ingestion guide for a walkthrough.

Reading fields

FieldTypeRequiredConstraints
system_identifierstringYes¹The System the reading comes from (1–255 chars)
machine_identifierstringYes¹The same thing under its earlier name; still accepted
timestampstringYesISO 8601 UTC; up to 5 minutes in the future and 3,650 days in the past
metric_namestringYes1–255 chars
valuenumberYesMust be finite (no NaN or Infinity)
unitstringYesUnit label, for example celsius, mm/s, psi
raw_tagstringNoOriginal tag name from the source system
idempotency_keystringNoA UUID that dedupes a reading across retries

¹ Send one of the two (both, if they agree). Each reading is routed to its System by this identifier, then to a machine by its tag: its raw_tag, or its metric_name when it has none. A tag the System has not sent before is created unassigned unless the System routes everything to its one machine (a machine created on its own; see Machines). A reading of an unassigned tag is stored but raises no alerts until the tag is assigned on the Systems page; assigning it brings those readings along.

curl -X POST https://api.haltless.io/api/v1/ingest \
-H "X-API-Key: <api-key>" \
-H "Content-Type: application/json" \
-d '{
"readings": [
{
"system_identifier": "CNC-001",
"timestamp": "2026-08-07T10:30:00Z",
"metric_name": "temperature",
"value": 72.5,
"unit": "celsius"
},
{
"system_identifier": "CNC-001",
"timestamp": "2026-08-07T10:30:00Z",
"metric_name": "vibration",
"value": 3.2,
"unit": "mm/s",
"raw_tag": "ns2_i1002"
}
]
}'

Response: 207 Multi-Status

{
"accepted_count": 2,
"rejected_count": 0,
"errors": [],
"unassigned_count": 0
}

When a reading names a System that is not one of your company's active Systems, the others are still stored and that reading is reported by its position in the batch. The entry's machine_identifier field holds the identifier the reading named:

{
"accepted_count": 1,
"rejected_count": 1,
"errors": [
{
"index": 1,
"machine_identifier": "UNKNOWN-001",
"error": "System identifier not found for this company: UNKNOWN-001"
}
],
"unassigned_count": 0
}

A reading that breaks the field constraints above is different: it fails the whole request with 422, and nothing in the batch is stored. See Errors.

Latest readings​

GET /api/v1/machines/{machine_id}/readings/latest

Returns the most recent reading for each metric, keyed by metric name.

{
"machine_id": "550e8400-e29b-41d4-a716-446655440000",
"readings": {
"temperature": {
"timestamp": "2026-08-07T10:30:00Z",
"value": 72.5,
"unit": "celsius",
"raw_tag": null
},
"vibration": {
"timestamp": "2026-08-07T10:30:00Z",
"value": 3.2,
"unit": "mm/s",
"raw_tag": "ns2_i1002"
}
}
}

Time-series history​

GET /api/v1/machines/{machine_id}/readings

Query historical readings, optionally downsampled to a fixed interval. When start and end are omitted, the last 24 hours are returned.

ParameterTypeDescription
startdatetimeStart of the range (ISO 8601 UTC)
enddatetimeEnd of the range (ISO 8601 UTC)
metricstringFilter to a single metric (up to 255 chars)
intervalstringDownsample bucket: 1m, 5m, 15m, 1h, 6h, 1d

Response: 200 OK

[
{
"metric_name": "temperature",
"unit": "celsius",
"data_points": [
{ "timestamp": "2026-08-07T00:00:00Z", "value": 71.2 },
{ "timestamp": "2026-08-07T00:05:00Z", "value": 72.1 }
]
}
]

Machine summary​

GET /api/v1/machines/{machine_id}/summary

Returns min, max, average, and latest values for each metric.

{
"machine_id": "550e8400-e29b-41d4-a716-446655440000",
"metrics": [
{
"metric_name": "temperature",
"unit": "celsius",
"min_value": 68.4,
"max_value": 87.3,
"avg_value": 73.1,
"latest_value": 72.5,
"latest_timestamp": "2026-08-07T10:30:00Z"
}
]
}

Tag mappings for a machine​

GET /api/v1/machines/{machine_id}/tag-mappings

Returns every tag assigned to the machine, with the name it was given (standardized_name is null for a tag nobody has named yet). To create or correct names, see Machines.

[
{
"id": "9b2f...",
"tenant_id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
"system_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"machine_id": "550e8400-e29b-41d4-a716-446655440000",
"raw_tag": "ns2_i1001_temp_motor_1",
"metric_name": "temperature",
"standardized_name": "motor_temperature",
"unit": "celsius",
"is_confirmed": true,
"confidence_score": 0.94,
"created_at": "2026-08-07T09:00:00Z",
"updated_at": "2026-08-07T09:05:00Z"
}
]

Metric baselines​

GET /api/v1/machines/{machine_id}/baselines

Returns computed baseline statistics (mean and standard deviation) for each metric.

[
{
"id": "a1b2...",
"machine_id": "550e8400-e29b-41d4-a716-446655440000",
"metric_name": "temperature",
"mean_value": 72.8,
"std_dev": 1.9,
"min_value": 68.4,
"max_value": 79.0,
"sample_count": 420,
"window_start": "2026-07-31T00:00:00Z",
"window_end": "2026-08-07T00:00:00Z"
}
]

Polling sources​

Polling sources let Haltless connect to an OPC-UA or Modbus endpoint and pull readings on a schedule, as an alternative to pushing to `/api/v1/ingest`. A source feeds a System, and its readings are routed to machines by tag exactly as pushed ones are. These endpoints require an operator or administrator role. A source is System set-up: creating or changing one requires access to every machine, and a user limited to some machine groups sees no sources (404 by id). Responses name the System (system_id, system_name); they no longer carry machine_id.

Create a source​

POST /api/v1/ingest-sources

FieldTypeRequiredDescription
system_iduuidYes¹System the source feeds
machine_iduuidYes¹Earlier form: the source feeds this machine's System
namestringYesSource name (1–200 chars)
protocolstringYesopcua or modbus (Modbus TCP). Spelled differently from a System's protocol (opc_ua, modbus_tcp)
hoststringYesExternally reachable host (1–255 chars)
portintegerYes1–65535
poll_interval_secondsintegerNo5–3600 (default 30)
is_activebooleanNoWhether polling is enabled (default true)
configobjectNoProtocol-specific settings

¹ Send system_id; machine_id is still accepted from older clients.

Response: 201 Created

{
"id": "c3d4...",
"tenant_id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
"system_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"system_name": "Line 3 PLC",
"name": "Line 3 OPC-UA",
"protocol": "opcua",
"host": "opc.example.com",
"port": 4840,
"poll_interval_seconds": 30,
"is_active": true,
"config": {},
"last_polled_at": null,
"last_error": null,
"created_at": "2026-08-07T10:00:00Z",
"updated_at": "2026-08-07T10:00:00Z"
}

List, get, update, and delete​

GET /api/v1/ingest-sources lists your sources and GET /api/v1/ingest-sources/{source_id} returns one. PUT /api/v1/ingest-sources/{source_id} updates a source and DELETE /api/v1/ingest-sources/{source_id} removes it (204 No Content).

Test a connection​

POST /api/v1/ingest-sources/{source_id}/test-connection attempts to connect to the configured endpoint and reports the result.

{
"success": true,
"message": "Connected successfully."
}