Appearance
Quickstart: from zero to first values
This chapter shows a minimal happy path from "I have a Coldwave backend URL" to "I see live device values in my terminal".
Runnable example
Everything in this quickstart exists as a complete, runnable Node.js project (plain ESM, one dependency, under 500 lines including comments):
Download coldwave-v5-getting-started-example.zip
Unpack it, npm install, copy .env.example to .env, fill in URL and credentials, npm start.
1. Prerequisites
You need:
- A running Coldwave backend instance, e.g.
https://<<URL>>. - An invite code from your administrator (or an existing account).
- Node.js 22 or newer for the code samples (
fetchand WebCrypto are used from core).
2. Create your account
An administrator invites you by email address (POST /user). You receive an invite code — either directly in your inbox or handed over out-of-band.
Redeem it once, choosing your own password. This endpoint is public, so plain curl works:
bash
curl --location 'https://<<URL>>/api/v1/user/register' \
--header 'Content-Type: application/json' \
--data '{
"code": "<<INVITE_CODE>>",
"password": "<<PASSWORD>>"
}'json
{ "resourceIdentifier": "crn#tenant:Ba9mN3pQ.user:Xk9mN2pQ", "emailVerified": true }- Passwords must be 16–64 characters — shorter ones are rejected.
- If the invite was mailed to you, your address counts as verified (
emailVerified: true) and you can log in right away. - Otherwise a verification code is on its way to the invite's address; confirm it with
POST /user/verify-emailbefore the first login.
3. Log in
v5 does not use plain bearer tokens: every request is signed with a key pair held by your client (DPoP), and the issued tokens are bound to that key. Three rules govern the whole session:
- One key pair per session — for login, every request, and every refresh.
- A fresh signed proof per request in the
DPoPheader. - A nonce handshake on the first call —
400 use_dpop_nonceplus aDPoP-Nonceheader is normal; retry with that value in the proof.
The whole client fits in one ~60-line class — take src/dpop.js from the example above, or copy it from Authentication & Sessions. With it, logging in is one call:
js
import { DpopClient } from "./dpop.js";
const client = await DpopClient.create();
const response = await client.fetch("POST", "https://<<URL>>/api/v1/auth/login", {
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "<<EMAIL>>", password: "<<PASSWORD>>" }),
});
const { accessToken, refreshToken } = await response.json();A 409 (email in several tenants) or status: "2fa_required" answer means an extra step — see Authentication & Sessions.
4. Make authenticated requests
Every authenticated call needs the access token and a fresh proof with the ath claim — the DpopClient handles both when you pass accessToken:
js
const devices = await client.fetch("GET", "https://<<URL>>/api/v1/devices?depth=2", {
accessToken,
});Access tokens live 10 minutes, refresh tokens 8 hours with rotation on every use — on a 401, refresh via POST /auth/token and retry once (token lifecycle).
5. List devices, services and properties
One request returns your whole fleet with all current values:
js
const response = await client.fetch(
"GET",
"https://<<URL>>/api/v1/devices?depth=2&expand=propertyType,modifier",
{ accessToken },
);
const devices = await response.json();json
[
{
"crn": "crn#tenant:Ba9mN3pQ.device:A1B2C3D4",
"imei": "350000000000001",
"services": [
{
"serviceId": "00000000-0000-2001-8003-006d0099ab53",
"properties": [
{ "id": "0x0800", "value": 215, "measuredAt": 1756800000000, "pending": null, "propertyType": "UINT16" }
]
}
]
}
]depth folds the hierarchy into one response — 0 devices only, 1 plus services, 2 plus every current property value. At this point you can already render a device list with services and raw property values. Direct routes for single devices and services, write operations and method calls: Devices & Properties.
6. Load schemas for human-readable labels
js
const response = await client.fetch("GET", "https://<<URL>>/api/v1/schema?depth=1", { accessToken });
const schemas = await response.json();The response maps each serviceIdentifier to names, units, enums and flags per property id. Build a local map from (serviceIdentifier, propertyId) to the schema entry and use it to show temperature instead of 0x0800, render enum dropdowns (off / silent / running), and respect readonly.
Property ids match directly — both sides use canonical hex strings ("0x0800"). Sample response, display formats and method declarations: Schemas.
7. Load metadata for nicer device lists
js
const response = await client.fetch("GET", "https://<<URL>>/api/v1/meta?depth=1", { accessToken });
const meta = await response.json();json
[
{
"crn": "crn#tenant:Ba9mN3pQ.device:A1B2C3D4",
"imei": "350000000000001",
"meta": {
"status": "online",
"lastMessage": 1756800000123,
"data": { "name": "Rooftop HVAC 1", "site": "Berlin", "floor": 4 }
}
}
]status and lastMessage are maintained by the backend; data is yours — use it to show "Rooftop HVAC 1 – Berlin" instead of a CRN. Writing metadata: Metadata.
8. Open the websocket and listen for updates
Fetch a single-use ticket (valid 30 seconds), then connect — the upgrade itself carries no auth header:
js
import WebSocket from "ws";
const { ticket } = await (await client.fetch(
"GET", "https://<<URL>>/api/v1/socket/ticket", { accessToken })).json();
const ws = new WebSocket(`wss://<<URL>>/api/v1/socket?ticket=${encodeURIComponent(ticket)}`);
ws.on("message", (raw) => {
const event = JSON.parse(raw.toString());
if (event.type === "HEALTHCHECK_PONG") return;
console.log(event.type, event.resourceIdentifier);
});There is no subscribe message — you receive every event of your tenant your account may read, e.g. property values as they arrive:
json
{
"type": "OBJECT_UPDATED",
"resourceIdentifier": "crn#tenant:Ba9mN3pQ.device:A1B2C3D4.service:00000000-0000-2001-8003-006d0099ab53",
"payload": { "updates": [ { "propId": 2048, "tag": "value", "value": 220, "measuredAt": 1756800000000 } ] }
}WARNING
propId is decimal here (2048 = 0x0800) — the one place the API departs from hex strings.
Apply incoming events on top of your REST snapshot and your UI stays live. Event catalog, heartbeat and reconnect strategy: WebSocket & Events.
Where to go next
- Write values and call device methods: Devices & Properties.
- Chart history: Historical Data.
- Something failing? Troubleshooting tables in Authentication & Sessions and WebSocket & Events.