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"]
  }'
FieldTypeNotes
timeFromnumberStart of the range, epoch milliseconds.
timeTonumberEnd of the range, epoch milliseconds.
timeZonestringIANA zone (e.g. Europe/Stockholm) the grouping aligns to.
groupingTypestringBucket size: MINUTES, HOURS, DAYS, MONTHS, or YEARS.
valueTypestringACTUAL (the reading) or DELTA (change over the bucket).
tagListstring[]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 Accept header — the trailing /json or /csv on the history endpoints selects it. Asking for csv streams 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:

StatusMeaning
400 Bad RequestThe request or body was invalid (bad field, malformed query).
401 UnauthorizedNo usable bearer token.
403 ForbiddenThe token isn’t allowed that project or resource.
404 Not FoundNo such project, tag, or page.