Using the API
Worked examples for the THMI public API — authenticating, reading values, querying history, error handling, and formats.
This page is the practical companion to the endpoint list: how to authenticate, what a history query body looks like, how timestamps and errors are shaped, and how to choose JSON or CSV. For the full, machine-readable contract, use the OpenAPI specification.
Authenticating
Send your access token as a bearer
token on every request. The header must start with Bearer — nothing else is
accepted, and there is no query-parameter fallback:
curl https://<your-cloud-host>/api/v1/projects/list \
-H "Authorization: Bearer <your-token>"
A request with a missing or malformed Authorization header is rejected with
401 Unauthorized before it reaches any project data. A valid token that
wasn’t granted the project or resource you’re asking for gets 403 Forbidden.
Reading a current value
Current values are plain GET requests. Fetch one tag by its path:
curl "https://<your-cloud-host>/api/v1/<projectId>/tag/values/by-path?tagPath=Building/Boiler/SupplyTemp" \
-H "Authorization: Bearer <your-token>"
Everything except history and CSV export is a GET — see
Endpoints for the full set (projects, tags, values,
pages, time zones).
Querying history
History is the one read that uses POST — not because it changes anything,
but because the query is too big for a URL. The body carries the time range, the
tags, and how to aggregate them. Pick the endpoint by how you identify tags
(by-path or by-id) and the response format by the last path segment
(json or csv):
curl -X POST "https://<your-cloud-host>/api/v1/<projectId>/history/aggregate/by-path/json" \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-d '{
"timeFrom": 1572548400000,
"timeTo": 1574239662847,
"timeZone": "Europe/Stockholm",
"groupingType": "HOURS",
"valueType": "ACTUAL",
"tagList": ["Building/Boiler/SupplyTemp", "Building/Boiler/ReturnTemp"]
}'
| Field | Type | Notes |
|---|---|---|
timeFrom | number | Start of the range, epoch milliseconds. |
timeTo | number | End of the range, epoch milliseconds. |
timeZone | string | IANA zone (e.g. Europe/Stockholm) the grouping aligns to. |
groupingType | string | Bucket size: MINUTES, HOURS, DAYS, MONTHS, or YEARS. |
valueType | string | ACTUAL (the reading) or DELTA (change over the bucket). |
tagList | string[] | Tag paths (for by-path) or tag ids (for by-id). |
For by-id, put tag ids in tagList instead of paths. The deprecated
…/history/aggregate/{format} (no by-path/by-id) behaves like by-id.
Timestamps
Every timestamp — the timeFrom/timeTo you send and the point times you get
back — is an epoch-milliseconds integer (UTC), not an ISO string. timeZone
only affects how buckets are aligned and labeled, not the units. Convert to and
from your own zone on your side.
Formats
- Responses are JSON by default. History and its export also produce CSV, and pages render as SVG.
- The format is chosen by the path, not an
Acceptheader — the trailing/jsonor/csvon the history endpoints selects it. Asking forcsvstreams the same data as a CSV download.
Errors
Errors come back as a small JSON envelope with a status of fail:
{
"status": "fail",
"message": "A human-readable explanation",
"code": "optional_error_code"
}
The HTTP status tells you the category:
| Status | Meaning |
|---|---|
| 400 Bad Request | The request or body was invalid (bad field, malformed query). |
| 401 Unauthorized | No usable bearer token. |
| 403 Forbidden | The token isn’t allowed that project or resource. |
| 404 Not Found | No such project, tag, or page. |