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.
| Method | Path | Purpose |
|---|---|---|
| GET | /cgg/devices | List devices |
| POST | /cgg/devices | Register a device |
| GET | /cgg/devices/logs | Logs across all accessible devices |
| GET | /cgg/devices/metrics | Metrics across all accessible devices |
| GET | /cgg/devices/:id | Get one device |
| PUT | /cgg/devices/:id | Update name, tags, public dashboard flag |
| DELETE | /cgg/devices/:id | Delete a device |
| GET | /cgg/devices/:id/config | Device configuration reported by the firmware |
| POST | /cgg/devices/:id/system/:command | Send a system command |
| GET | /cgg/devices/:id/logs | Logs for one device |
| DELETE | /cgg/devices/:id/logs | Delete logs for one device |
| GET | /cgg/devices/:id/metrics | Metrics for one device |
| GET | /cgg/devices/:id/variables/:variable? | Read state variables |
| POST | /cgg/devices/:id/variables/:variable | Set a variable |
| GET | /cgg/devices/:id/variables/:variable/changes | Pending variable changes |
| DELETE | /cgg/devices/:id/variables/changes | Discard all pending variable changes |
| GET | /cgg/devices/:id/tables/:table | Read a data table |
| GET | /cgg/devices/:id/tables/:table/count | Row count of a data table |
| GET | /cgg/devices/:id/tables/:table/changes | Pending table changes |
| POST | /cgg/devices/:id/tables/:table/changes | Change a data table |
| DELETE | /cgg/devices/:id/tables/changes | Discard all pending table changes |
| POST | /cgg/devices/:id/commands/:command | Send an application command |
| POST | /cgg/devices/:id/firmware | Assign firmware (OTA) |
| DELETE | /cgg/devices/:id/firmware/changes | Cancel 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 parameter | Description |
|---|---|
start | Offset, default 0 |
count | Page size, default 10 |
search | Case-insensitive match on device name |
workspace | Return devices of this workspace instead of personal devices |
tenant | Restrict to a tenant |
group | Restrict to a device group (_id of the group) |
devices | Comma-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 parameter | Description |
|---|---|
start | Offset, default 0 |
count | Page size — default 20 for a single device, 10 for the fleet endpoint |
search | Case-insensitive match on the log message (data) |
startTime / endTime | Time range, defaults to the last 24 hours |
workspace, tenant | Fleet 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.
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/devices — no authentication. Only devices with
publicDashboard: true are reachable; anything else returns 404.
| Method | Path | Returns |
|---|---|---|
| GET | /cgg/public/devices/:id | The device object with key and user removed |
| GET | /cgg/public/devices/:id/variables | All state variables |
| GET | /cgg/public/devices/:id/variables/:variable | One variable (or {}) |
| GET | /cgg/public/devices/:id/tables/:table | Table contents |
| GET | /cgg/public/devices/:id/tables/:table/count | Row count as plain text |
| GET | /cgg/public/devices/:id/config | Device configuration |
These endpoints are read-only; there is no public way to write variables, tables or commands.