Tags
Tags are the gateway's data points: folders, driver-bound tags, memory tags, derived tags, expression tags and tag type instances. Configuration lives under /api/v1/tags; live values are read and written under /api/v1/runtime/tags. For what the fields mean, see Tags.
Every {id} below accepts the tag's id. The runtime routes also accept the tag's path (Plant.Line1.Setpoint) in place of the id.
Routes
| Method and path | Role | Purpose |
|---|---|---|
GET /api/v1/tags | viewer | List tags, optionally one folder level or one connection's tags |
GET /api/v1/tags/{id} | viewer | One tag in full |
GET /api/v1/tags/{id}/resolve | viewer | The binding and source a tag resolves to (parameters substituted for instance members) |
GET /api/v1/tags/bit-maps | viewer | Which derived tags read which bit of which word |
GET /api/v1/tags/export?path= | viewer | Export a tag or folder with everything under it |
POST /api/v1/tags/import | engineer | Import an export under a target path |
POST /api/v1/tags | engineer | Create a tag or folder |
PATCH /api/v1/tags/{id} | engineer | Update a tag |
POST /api/v1/tags/{id}/move | engineer | Move a tag or folder to another parent |
POST /api/v1/tags/{id}/copy | engineer | Deep-copy a tag or folder under a new name |
POST /api/v1/tags/{id}/restart | operator | Make the tag's source send its current value again |
DELETE /api/v1/tags/{id} | engineer | Delete a tag, with everything under it |
GET /api/v1/runtime/tags/{id}/current | viewer | Current value and quality |
GET /api/v1/runtime/tags/not-good | viewer | Every tag whose current quality is not good |
POST /api/v1/runtime/tags/{id}/write | operator | Write a value through the tag's owner (driver or memory) |
POST /api/v1/runtime/tags/{id}/values | engineer | Inject a reading as if the source reported it |
GET /api/v1/tags/{id}/alarms | viewer | The tag's alarm definitions (see Alarms) |
Per-tag audit: there is no route under /tags; filter the admin audit log with GET /api/v1/admin/audit/tag-writes?tag= (see Administration).
Listing
GET /api/v1/tags takes two optional query parameters:
| Parameter | Meaning |
|---|---|
parentPath | Only the direct children of this folder. null (the literal string) returns the root level. Omitted returns every tag |
producerInstanceId | Only tags bound to this driver connection |
The root listing always ends with the virtual @gateway folder. Listing under @gateway or @gateway.* returns the gateway's own tags (@gateway.performance, @gateway.connection.*, @gateway.clients, @gateway.browser.*), some of which are virtual (no database row) and some real. Gateway-managed tags cannot be changed, moved or deleted (400), but alarms can be added to them. See Gateway metrics as tags.
Each tag in the list has:
{
"id": "…", "path": "Plant.Line1.Setpoint", "name": "Setpoint", "parentPath": "Plant.Line1",
"enabled": true, "kind": "memory", "dataType": 4,
"udtInstanceId": null, "udtMemberKey": null, "overrides": null, "udtRootMemberKey": null, "udtDocumentKey": null,
"createdAt": "2026-11-08T12:00:00+00:00", "updatedAt": "…",
"binding": { "moduleId": "io.scadex.opcua", "instanceId": "…", "configuration": { } },
"derived": { }, "memory": { }, "expression": { }, "meta": { }, "numeric": { }
}
binding, derived, memory, expression, meta and numeric are null when the tag has no settings of that kind. GET /api/v1/tags/{id} returns the same shape; for a member of a tag type instance it returns a shorter record with udtInstance: { id, typeId, parameters, type: { id, path } }, because the type defines the member's fields.
Creating a tag
POST /api/v1/tags with CreateTagDto. path and kind are required; everything else is optional and depends on the kind. name and parentPath are derived from path; if sent they are ignored.
| Field | Type | Applies to | Meaning |
|---|---|---|---|
path | string | all | Full dotted path. Names starting with @ are reserved (400) |
kind | string | all | folder, memory, derived, expression, udt (an instance root), or a driver tag with a binding |
enabled | boolean | all | Default true. Folders are always enabled |
dataType | integer | all | Data type code |
producerModuleId | string | driver | The driver module's id, for example io.scadex.opcua |
producerInstanceId | string | driver | The connection's id |
binding | object | driver | Driver-specific address, the shape the driver's descriptor defines |
sourceTag | string | derived | Path of the tag to follow |
property | string | derived | Which property of the source to follow |
sourceBit | integer | derived | Follow one bit of the source; 0 is the lowest |
expression | string | expression | The expression; see Expression tags |
memoryStartValue | string | memory | Start value, as text |
scope | string | memory | gateway (default), user or session |
tooltip, documentation | string | all | Notes |
engUnit | string | all | Engineering unit |
formatString | string | all | Display format |
scaleMode | string | numeric | linear, squareRoot, bits or map; absent means no scaling |
rawLow, rawHigh, engLowLim, engHighLim | number | numeric | Raw and engineering ranges for linear/squareRoot |
engLimitMode | string | numeric | clamp to limit to the engineering range |
scaleFactor | number | numeric | Multiplier |
bitCount | integer | numeric | For bits |
scaleMap | string | numeric | For map: the value map, as text |
deadbandMode | string | numeric | absolute or percent |
deadband | number | numeric | Deadband amount |
Scaling and deadband details are in Tags. An instance (kind: "udt") cannot be created inside another instance (400). A tag created under an instance's path becomes a tag added to that instance. History settings are not fields on the tag; the historian module decides what it stores (see History).
Response 200: the created tag in the list shape. A path already taken answers 409.
Updating a tag
PATCH /api/v1/tags/{id} with UpdateTagDto: every field of the create model except path and scope-independent ones, plus:
| Field | Type | Meaning |
|---|---|---|
name | string | Renames the tag (the last path segment); descendants are renamed along. An instance root keeps its name |
kind | string | Changes the kind |
memoryValue | any | Memory tags: sets the current value. An explicit null resets to the start value; absent leaves it alone |
overrides | object | Instance members: a patch of member fields for this instance; replaces the stored overrides |
sourceBit | integer | 0 to 63; -1 stops following a bit |
Fields that are absent stay as they are. Response 200: the updated tag.
Moving, copying and deleting
POST /api/v1/tags/{id}/movebody{"newParentPath": "Plant.Line2"};nullmoves to the root. Member tags of an instance and anything under@typescannot be moved (400). Response: the moved tag.POST /api/v1/tags/{id}/copybody{"newName": "Setpoint2", "targetParentPath": "Plant.Line1"}(targetParentPathnullmeans the root). Deep-copies the tag or folder with every descendant renamed along. Member tags cannot be copied (copy the instance), nor can types (400). Response: the copy.DELETE /api/v1/tags/{id}answers204.POST /api/v1/tags/{id}/restartmakes drivers re-create their subscriptions and rebuilds computed tags for the tag and everything under it (a folder, an instance, a type or a folder of types). Response:{"tags": 12, "driverTags": 8, "connections": 1}.
Export and import
GET /api/v1/tags/export?path=Plant.Line1 returns:
{ "version": 1, "type": "…", "exportedPath": "Plant.Line1", "tags": [ { "relativePath": "Setpoint", "kind": "memory", "enabled": true, "producerModuleId": null, "producerInstanceId": null, "binding": null, "dataType": 4, "sourceTag": null, "property": null, "expression": null, "memoryStartValue": "0", "memoryValue": "42", "tooltip": null, "documentation": null, "engUnit": null, "formatString": null, "scope": null, "sourceBit": null } ] }
POST /api/v1/tags/import body {"targetPath": "Plant.Line2", "data": <the export>} creates the tags under targetPath. Answers 204. Types are exported and imported through their own routes (see Tag types).
Current value
GET /api/v1/runtime/tags/{id}/current answers 404 {"message": "No current value is available for this tag."} until the tag has a value, otherwise:
{
"tag": { "id": 17, "publicId": "…", "path": "Plant.Line1.Setpoint", "kind": "memory", "enabled": true, "dataType": 4, "unit": null, "displayName": null, "description": null, "producerModuleId": null, "producerInstanceId": null },
"update": { "tagId": 17, "sequence": 120, "timelineSequence": 90311, "value": 42, "quality": { "code": "good", "detail": null }, "timestamp": "2026-11-08T12:00:00.123+00:00", "correlationId": null, "producerModuleId": null, "rawValue": null }
}
timestamp is ISO-8601. rawValue is the source's value when scaling changed it. For a user-scoped memory tag the caller's own value replaces the shared start value.
GET /api/v1/runtime/tags/not-good returns [{ "path": "…", "quality": "bad" | "uncertain" }] for every tag with a stored value whose quality is not good.
Writing a value
POST /api/v1/runtime/tags/{id}/write (operator) body:
| Field | Type | Required | Meaning |
|---|---|---|---|
value | any JSON | yes | The value to write |
correlationId | string | no | Carried into the resulting value update and the audit log |
Response 202 when accepted, 400 when refused, both with the same body:
{ "accepted": true, "update": { "...": "a TagValueUpdate as above" }, "error": null }
Every attempt is recorded in the write audit log (see Audit log). Writes to @gateway.browser.* are not tag writes and are not logged.
Injecting a reading
POST /api/v1/runtime/tags/{id}/values (engineer) publishes a value as if the tag's source had reported it, skipping the driver:
| Field | Type | Required | Meaning |
|---|---|---|---|
value | any JSON | yes | |
quality | string | no | good (default), uncertain or bad; anything else is 400 |
qualityDetail | string | no | Free text attached to the quality |
timestamp | ISO-8601 string | no | Defaults to now |
correlationId | string | no |
Response 202 with the resulting TagValueUpdate. Refusals (unknown tag, disabled tag) answer 400 {"statusCode":400,"message":"…"}. Audit rows for this route carry operation publish.