Skip to content

A complete worked example

A from-scratch scenario with curl. First set the variables from the API introduction: CELESTIAN, TOKEN, and WS. The responses below were taken from a running backend (IDs shortened; <...> = fill in your own).

Terminal window
curl -s "$CELESTIAN/api/v1/workspaces/$WS" -H "Authorization: Bearer $TOKEN"
{ "workspace": { "id": "…", "name": "Cold Storage Nusantara", "route": "cold-storage", "status": "enabled", "tags": ["demo"] },
"permissions": ["workspace.read", "device.read", "device.write", "channel.write", "rule.write", "…"],
"memberCount": 1, "owner": { "id": "…", "username": "…" }, "isOwner": true, "pendingTransfer": null }

permissions are this token’s effective rights. Check them when an action is refused.

Terminal window
curl -s -X POST "$CELESTIAN/api/v1/workspaces/$WS/channels" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -d '{"name":"Warehouse Telemetry","route":"warehouse-telemetry","tags":["telemetry"]}'

201 → {"channel": {"id": "<channel-id>", "name": "Warehouse Telemetry", "route": "warehouse-telemetry", "status": "enabled", …}}. route is optional (lowercase letters, digits, -).

Terminal window
curl -s -X POST "$CELESTIAN/api/v1/workspaces/$WS/devices" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -d '{"name":"Freezer Room A1 Sensor","serial":"SN-A1","tags":["freezer-room"]}'
{ "device": { "id": "<device-id>", "name": "Freezer Room A1 Sensor", "serial": "SN-A1", "isGateway": false,
"status": "enabled", "keyRotatedAt": "…", "tags": ["freezer-room"] },
"key": "BVZvgFsU…" }

key is shown only once. It is the device’s MQTT password. Regenerate it with POST .../devices/{id}/key (a body of {} for a random key, or {"key": "<your-own-value>"}). Send "isGateway": true to create a gateway.

Terminal window
curl -s -X POST "$CELESTIAN/api/v1/workspaces/$WS/connections" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"deviceIds":["<device-id>"],"channelIds":["<channel-id>"],"types":["publish"]}'

204. types: publish (the device sends) and/or subscribe (the device receives). Requires device.connect and channel.connect, at most 100 pairs per request. Disconnect with POST .../connections/disconnect (the same body).

Terminal window
curl -s -X POST "$CELESTIAN/api/v1/workspaces/$WS/rules/quick-store" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -d '{"channelId":"<channel-id>","format":"senml"}'

201 → the rule Channel Subscriber (all subtopics) → Bypass → Internal DB (SenML). format: senml (numeric, boolean, and text metrics; supports latest values, aggregation, and charts) or json (free-form payloads). 409 when that channel already has an active storage rule.

Terminal window
mosquitto_pub -h <broker> -p 1883 -V mqttv311 -i <device-id> -u <device-id> -P '<key>' \
-t "m/$WS/c/warehouse-telemetry/room-a1" \
-m '[{"bn":"room-a1:","n":"temp_inlet","u":"Cel","v":23.5},{"n":"humidity","u":"%RH","v":41.2},{"n":"door_open","vb":false}]'
Terminal window
curl -s "$CELESTIAN/api/v1/workspaces/$WS/messages?channelId=<channel-id>&format=senml&limit=3" \
-H "Authorization: Bearer $TOKEN"
{ "page": {
"format": "senml",
"senml": [
{ "time": "2026-10-02T22:42:05.432+07:00", "publisher": "<device-id>", "deviceId": "room-a1:",
"subtopic": "room-a1", "protocol": "mqtt", "name": "room-a1:humidity", "unit": "%RH",
"value": 41.2, "stringValue": null, "boolValue": null, "dataValue": null, "sum": null, "updateTime": null }
],
"nextCursor": "MjAyNi0xMC0wMlQx…", "from": "2026-09-25T15:42:24Z", "to": null } }

Without from and to, the last 7 days are shown. Other filters: subtopic, publisher, deviceId, name, protocol, valueOp (eq|ne|gt|gte|lt|lte) with valueNum and valueType, and sort=asc|desc. publisher is the authenticated sending device’s ID, not taken from the payload.

8. Metric names, aggregation, and device status

Section titled “8. Metric names, aggregation, and device status”
Terminal window
curl -s "$CELESTIAN/api/v1/workspaces/$WS/messages/names?channelId=<channel-id>" -H "Authorization: Bearer $TOKEN"
# {"names": ["room-a1:humidity", "room-a1:temp_inlet"]}
curl -s "$CELESTIAN/api/v1/workspaces/$WS/messages/aggregate?channelId=<channel-id>&name=room-a1:temp_inlet&interval=1h" \
-H "Authorization: Bearer $TOKEN"
# {"interval":"1h","buckets":[{"time":"2026-10-02T22:00:00+07:00","name":"room-a1:temp_inlet","min":23.5,"max":23.5,"avg":23.5,"sum":47,"count":2}],"truncated":false,"from":"…"}
curl -s "$CELESTIAN/api/v1/workspaces/$WS/presence/devices?ids=<device-id>" -H "Authorization: Bearer $TOKEN"
# {"health": {"<device-id>": {"online": false, "clients": 0, "lastConnectAt": "…", "lastDisconnectAt": "…", "protocol": "mqtt"}},
# "presence": {"<device-id>": {"lastSeen": "…", "protocol": "mqtt"}}}

interval: 1m, 5m, 15m, 1h, 6h, 1d. CSV export (at most 50,000 rows): GET .../messages/export.csv with the same filters.

9. Send a message to a channel from the API

Section titled “9. Send a message to a channel from the API”

Requires channel.publish.

Terminal window
curl -s -X POST "$CELESTIAN/api/v1/workspaces/$WS/messages/send" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"channelId":"<channel-id>","subtopic":"room-a1","payload":"[{\"n\":\"temp_inlet\",\"u\":\"Cel\",\"v\":24.1}]"}'

204. payload is a string (the message content as is, at most 256 KB). The message is recorded with the ui protocol and processed by rules like a device message. The optional "form": "uuid" | "route" chooses the destination topic form (the broker matches topics literally).

Dashboards and rules are the same JSON documents you download from the application. The easiest way to learn them: create one through the UI, then GET it and modify.

Terminal window
# list dashboards (without widgets)
curl -s "$CELESTIAN/api/v1/workspaces/$WS/dashboards" -H "Authorization: Bearer $TOKEN"
# one rule with its graph
curl -s "$CELESTIAN/api/v1/workspaces/$WS/rules/<rule-id>" -H "Authorization: Bearer $TOKEN"