Skip to main content

Workflows

A workflow is a server-side automation: one trigger followed by a chain of nodes. Workflows run in the cloud, independently of the devices, and each execution is recorded as a run.

Base path: /cgg/workflows (authentication required — API key or session) and /cgg/webhooks (public).

MethodPathPurpose
GET/cgg/workflowsList workflows
GET/cgg/workflows/:idGet one workflow
POST/cgg/workflowsCreate a workflow
PUT/cgg/workflows/:idUpdate a workflow
DELETE/cgg/workflows/:idDelete a workflow and its runs
GET/cgg/workflows/:id/runsList runs of a workflow
ALL/cgg/webhooks/:idTrigger a webhook workflow (public)

Access rules

Personal workflows are reachable only by their creator. Workspace workflows are reachable by the workspace owner, admins and members — not by operators or tenant-only members, who get 403 {"message":"No access to this workspace workflow"}.

The workflow object

{
"_id": "664d1a2b8c9d0e0011229a01",
"name": "Alert on high temperature",
"description": "",
"enabled": true,
"retrigger": false,
"workspace": "664a1f2b8c9d0e0011223344",
"trigger": {
"type": "DeviceEvent",
"properties": {
"filter": "device",
"device": "665f3c1e9d1b4a0012a7c8d1"
}
},
"nodes": [
{
"id": "node-1",
"name": "Check threshold",
"type": "Condition",
"properties": { "expression": "input.value > 90" }
},
{
"id": "node-2",
"name": "Notify",
"type": "Email",
"properties": { "to": "ops@acme.example", "subject": "High temperature" }
}
],
"createdAt": "…",
"updatedAt": "…"
}

Triggers

trigger.type selects how the workflow starts:

TypepropertiesFires when
Timeschedule — a cron expressionThe schedule elapses
Webhookurl — the webhook path segment/cgg/webhooks/<url> is called
DeviceEventfilter, device, groupA device reports a metric/event
DeviceUpdatefilter, device, groupA device reports new state

For the device triggers, filter is device (match properties.device against the reporting deviceId), group (match properties.group against the device's group) or unset to match every device in the same scope as the workflow.

Time triggers are validated against node-cron; an invalid expression is logged and the workflow is simply never scheduled.

Nodes

node.type selects the step implementation:

TypePurpose
ConditionEvaluate an expression and stop or continue
HTTPCallCall an external HTTP endpoint
EmailSend an email
DeviceActionWrite a device variable, table or command
CustomFunctionRun a user-supplied JavaScript function

node.properties is free-form and interpreted by the node implementation. Nodes execute in array order and each receives the accumulated run data. Workspace variables are made available to nodes of workspace workflows.

List workflows

GET /cgg/workflows
Query parameterDescription
startOffset, default 0
countPage size, default 10
searchCase-insensitive match on name
workspaceReturn that workspace's workflows instead of personal ones

Returns the standard { count, data } envelope.

Get one workflow

GET /cgg/workflows/:id

Returns the workflow document.

Create a workflow

POST /cgg/workflows
{
"name": "Alert on high temperature",
"trigger": { "type": "Webhook", "properties": { "url": "boiler-alert-9f2a" } },
"nodes": [],
"workspace": "664a1f2b8c9d0e0011223344"
}

New workflows are always created disabled (enabled: false) — enable them with a PUT once the nodes are in place. The created workflow is returned.

For Webhook triggers, choose an unguessable url value: the webhook endpoint is public and the value is the only secret protecting it.

Update a workflow

PUT /cgg/workflows/:id

The body is merged into the workflow with $set, so send the fields you want to change:

{ "enabled": true }

Notes on behaviour:

  • _id, user and workspace in the body are ignored — a workflow cannot be moved between users or workspaces.
  • If both enabled and trigger are absent from the body, the existing trigger is unset. Always include the trigger when saving a workflow's structure.
  • Toggling enabled reschedules Time triggers immediately and writes a workflow audit log entry.

The response is 200 with an empty body.

Delete a workflow

DELETE /cgg/workflows/:id

Unschedules the workflow and deletes all of its runs along with the workflow itself.

Workflow runs

GET /cgg/workflows/:id/runs
Query parameterDescription
startOffset, default 0
countPage size, default 10, clamped to 1–100
{
"count": 214,
"data": [
{
"_id": "664d2b3c8c9d0e0011229a55",
"workflow": "664d1a2b8c9d0e0011229a01",
"completed": true,
"data": { "value": 93.4, "device": "665f3c1e…" },
"results": [
{ "node": "node-1", "output": true },
{ "node": "node-2", "output": { "sent": true } }
],
"createdAt": "2026-08-17T05:58:00.000Z",
"updatedAt": "2026-08-17T05:58:01.000Z"
}
]
}

data is the trigger input, results the per-node output, and completed indicates whether the chain finished. Runs are sorted newest first.

Retention

Workflow runs are deleted automatically after 3 days.

Webhook trigger

ALL /cgg/webhooks/:id

Public endpoint — no session required. :id is matched against trigger.properties.url of a workflow whose trigger type is Webhook; no match returns 400.

Any HTTP method is accepted. The request body is read as plain text (express.text()), so send Content-Type: text/plain — or any content type — and parse it inside the workflow if you need structured data.

The workflow receives:

{
"url": "/cgg/webhooks/boiler-alert-9f2a?zone=2",
"method": "POST",
"headers": { "content-type": "text/plain", "…": "…" },
"query": { "zone": "2" },
"body": "raw request body as text"
}

The response is 200 with an empty body and is sent before the workflow runs — it acknowledges receipt, not execution. A disabled workflow accepts the call and does nothing. Check GET /cgg/workflows/:id/runs to see the outcome.

curl -X POST 'https://api.appblocks.io/cgg/webhooks/boiler-alert-9f2a?zone=2' \
-H 'Content-Type: text/plain' \
--data 'temperature=93.4'