Skip to main content

Devices

Base path: /cgg/devices — every route requires authentication, either an API key or a signed-in session.

The :id path parameter is always the device's deviceId, which is the string form of the device's Mongo _id.

Access is checked per request: a device with no workspace is only reachable by its creator; a workspace device is reachable by anyone with access to that workspace. Workspace operators get read-only access. Tenant-only members without the tenant admin role cannot write variables or tables.

MethodPathPurpose
GET/cgg/devicesList devices
POST/cgg/devicesRegister a device
GET/cgg/devices/logsLogs across all accessible devices
GET/cgg/devices/metricsMetrics across all accessible devices
GET/cgg/devices/:idGet one device
PUT/cgg/devices/:idUpdate name, tags, public dashboard flag
DELETE/cgg/devices/:idDelete a device
GET/cgg/devices/:id/configDevice configuration reported by the firmware
POST/cgg/devices/:id/system/:commandSend a system command
GET/cgg/devices/:id/logsLogs for one device
DELETE/cgg/devices/:id/logsDelete logs for one device
GET/cgg/devices/:id/metricsMetrics for one device
GET/cgg/devices/:id/variables/:variable?Read state variables
POST/cgg/devices/:id/variables/:variableSet a variable
GET/cgg/devices/:id/variables/:variable/changesPending variable changes
DELETE/cgg/devices/:id/variables/changesDiscard all pending variable changes
GET/cgg/devices/:id/tables/:tableRead a data table
GET/cgg/devices/:id/tables/:table/countRow count of a data table
GET/cgg/devices/:id/tables/:table/changesPending table changes
POST/cgg/devices/:id/tables/:table/changesChange a data table
DELETE/cgg/devices/:id/tables/changesDiscard all pending table changes
POST/cgg/devices/:id/commands/:commandSend an application command
POST/cgg/devices/:id/firmwareAssign firmware (OTA)
DELETE/cgg/devices/:id/firmware/changesCancel a pending firmware assignment

The device object

{
"_id": "665f3c1e9d1b4a0012a7c8d1",
"deviceId": "665f3c1e9d1b4a0012a7c8d1",
"key": "e6b1d9a4-5c3f-4c2e-9f7a-1d2b3c4d5e6f",
"name": "Boiler room controller",
"workspace": "664a1f2b8c9d0e0011223344",
"tenant": "664a20338c9d0e0011223399",
"group": "664a20fa8c9d0e00112233aa",
"tags": ["boiler", "floor-2"],
"publicDashboard": false,
"config": {
"variables": {},
"tables": {}
},
"state": {
"connected": true,
"firmwareVersion": "1.4.2",
"appId": "664a1f2b8c9d0e0011223301",
"appVersion": "12",
"appName": "Boiler control",
"mac": "0.36.53.1.2.3",
"ip": "192.168.1.44",
"bootTime": "2026-08-14T07:11:02.000Z",
"timezone": "Europe/Riga",
"variables": {
"setpoint": { "name": "setpoint", "value": 21.5, "updatedAt": "…" }
},
"tables": {
"readings": { "name": "readings", "value": "…csv…", "updatedAt": "…" }
}
},
"changes": {
"variables": {},
"tables": {},
"firmware": {}
},
"createdAt": "2026-05-04T10:22:00.000Z",
"updatedAt": "2026-08-17T06:00:11.000Z"
}

Three sections carry device data:

  • config — the structure declared by the running application (which variables and tables exist, their types and constraints).
  • state — the last values reported by the device, plus connection and runtime information.
  • changes — values written from the cloud that the device has not yet confirmed. Writes go here first and are pushed to the device over MQTT; the entry disappears once the device reports the new value.

The key field is the device's MQTT credential. It is returned to the device owner and is stripped from public-dashboard responses.

List devices

GET /cgg/devices
Query parameterDescription
startOffset, default 0
countPage size, default 10
searchCase-insensitive match on device name
workspaceReturn devices of this workspace instead of personal devices
tenantRestrict to a tenant
groupRestrict to a device group (_id of the group)
devicesComma-separated list of device _ids to return
<field>___<op>Field filters, e.g. state.connected___eq=true
{
"count": 42,
"data": [ /* device objects */ ]
}

Returns 403 {"message":"No access to this workspace"} if workspace is not accessible to the caller.

Register a device

POST /cgg/devices
{
"name": "Boiler room controller",
"workspace": "664a1f2b8c9d0e0011223344",
"tenant": "664a20338c9d0e0011223399"
}

workspace and tenant are optional; omit both to create a personal device. The server generates the deviceId and the MQTT key, and returns the full device object — use deviceId and key when provisioning the physical device.

Adding a device to a tenant requires either the tenant admin role or workspace admin rights. Workspace operators cannot create devices.

Get, update and delete

GET    /cgg/devices/:id
PUT /cgg/devices/:id
DELETE /cgg/devices/:id

GET returns the device object with the user field removed.

PUT body:

{
"name": "New name",
"tags": ["boiler", "floor-2"],
"publicDashboard": true
}

name is always applied; tags and publicDashboard are applied only when present. Setting publicDashboard: true exposes the device through the public endpoints. PUT and DELETE return 200 with an empty body.

Device configuration

GET /cgg/devices/:id/config

Returns the config object — the variable and table definitions published by the running application. Returns {} when the device has not reported a configuration yet.

Variables

GET  /cgg/devices/:id/variables            # all variables
GET /cgg/devices/:id/variables/:variable # one variable
POST /cgg/devices/:id/variables/:variable # write a value
GET /cgg/devices/:id/variables/:variable/changes
DELETE /cgg/devices/:id/variables/changes

Reading all variables returns the state.variables map:

{
"setpoint": { "name": "setpoint", "value": 21.5, "updatedAt": "2026-08-17T05:59:00.000Z" },
"mode": { "name": "mode", "value": "auto", "updatedAt": "2026-08-16T18:02:11.000Z" }
}

Reading a single, unknown variable returns {} rather than a 404.

Writing a value:

POST /cgg/devices/:id/variables/setpoint
{ "value": 22 }

The value is published to the device over MQTT and recorded under changes.variables.setpoint. The response is 200 with an empty body — it confirms the write was queued, not that the device applied it. Poll the variable or its changes entry to observe the result. Every write is recorded in the audit log.

DELETE /cgg/devices/:id/variables/changes clears the retained MQTT messages for all pending variable changes and removes changes.variables.

Data tables

GET  /cgg/devices/:id/tables/:table
GET /cgg/devices/:id/tables/:table/count
GET /cgg/devices/:id/tables/:table/changes
POST /cgg/devices/:id/tables/:table/changes
DELETE /cgg/devices/:id/tables/changes

Table contents are transported as CSV text with \r\n line endings, the first line being the header. …/count returns the number of data rows as a plain text number (0 when the table is empty or unknown).

Writing to a table:

POST /cgg/devices/:id/tables/readings/changes
{
"action": "insert",
"row": { "timestamp": "2026-08-17T06:00:00Z", "value": "21.9" }
}

The whole request body is stored under changes.tables.<table> and forwarded to the device. When action is "import", the data property (full CSV content) replaces the table:

{ "action": "import", "data": "time,value\r\n2026-08-17T06:00:00Z,21.9\r\n" }

Table changes are recorded in the audit log with the action and affected row. DELETE /cgg/devices/:id/tables/changes reverts all pending table changes.

Commands

Two command endpoints exist:

POST /cgg/devices/:id/commands/:command   # application command, with argument
POST /cgg/devices/:id/system/:command # system command, no argument

Application commands accept an argument and are audit-logged:

{ "data": "restart-pump" }

The value is stringified and published to devices/<deviceId>/commands/<command> as {"data":"<value>"}. System commands publish to the same topic with no payload and are not audit-logged. Both return 200 with an empty body.

Firmware (OTA)

POST   /cgg/devices/:id/firmware
DELETE /cgg/devices/:id/firmware/changes

Assigning firmware:

{
"url": "https://api.appblocks.io/api/projects/<projectId>/versions/<versionCode>/firmware/<projectId>_<versionCode>_1.4.3.tcu",
"workspaceId": "664a1f2b8c9d0e0011223344"
}

The url must be the url of an existing project version. The device is instructed over MQTT to download it, and changes.firmware records the target version:

{
"changes": {
"firmware": {
"version": "13",
"firmware": "1.4.3",
"url": "https://…/firmware/….tcu",
"app": "664a1f2b8c9d0e0011223301"
}
}
}

workspaceId is used only for the audit log entry. If no project version matches the URL, the request still returns 200 but nothing is recorded.

DELETE /cgg/devices/:id/firmware/changes clears the retained firmware message and the changes.firmware entry, cancelling an update that has not started.

To upgrade many devices at once, use device group firmware assignment.

Logs

GET    /cgg/devices/:id/logs   # one device
DELETE /cgg/devices/:id/logs # delete one device's logs
GET /cgg/devices/logs # all accessible devices
Query parameterDescription
startOffset, default 0
countPage size — default 20 for a single device, 10 for the fleet endpoint
searchCase-insensitive match on the log message (data)
startTime / endTimeTime range, defaults to the last 24 hours
workspace, tenantFleet endpoint only — scope the device set
<field>___<op>Field filters applied to log entries
{
"count": 1284,
"data": [
{
"_id": "66c0…",
"device": { "name": "Boiler room controller", "deviceId": "665f…" },
"data": "Pump started",
"createdAt": "2026-08-17T05:58:44.000Z"
}
]
}

The fleet endpoint resolves the 100 most recently updated devices in scope and returns their logs. DELETE accepts the same field filters, so you can delete a subset instead of the whole history.

Retention

Device logs, metrics and workflow runs are pruned automatically. Logs and metrics are kept for 3 weeks; workflow runs for 3 days.

Metrics

GET /cgg/devices/:id/metrics   # one device
GET /cgg/devices/metrics # all accessible devices

Metrics are named numeric events reported by devices — the source of historical charts. Query parameters match the log endpoints, with two differences: search filters on the metric name for a single device, and the fleet endpoint uses filter instead of search for the same purpose.

{
"count": 5760,
"data": [
{
"_id": "66c0…",
"device": { "name": "Boiler room controller", "deviceId": "665f…" },
"name": "temperature",
"value": 21.9,
"createdAt": "2026-08-17T05:58:00.000Z"
}
]
}

Public device access

Base path: /cgg/public/devicesno authentication. Only devices with publicDashboard: true are reachable; anything else returns 404.

MethodPathReturns
GET/cgg/public/devices/:idThe device object with key and user removed
GET/cgg/public/devices/:id/variablesAll state variables
GET/cgg/public/devices/:id/variables/:variableOne variable (or {})
GET/cgg/public/devices/:id/tables/:tableTable contents
GET/cgg/public/devices/:id/tables/:table/countRow count as plain text
GET/cgg/public/devices/:id/configDevice configuration

These endpoints are read-only; there is no public way to write variables, tables or commands.