Skip to content

Relations

Relations describe connections between two entities as a directed triple:

subject -> predicate -> object

The predicate defines the semantics and how to interpret the relation from the subject's perspective. For example, Asset A -> found_by_gateway -> Gateway B means that Asset A has been found by Gateway B and not the other way around. The subject is the entity the relation starts from, and the object is the entity the relation points to. Swapping them changes the meaning of the relation.

Relation Entities

An asset is managed by Inventory. A node belongs to an external information domain, such as the Context Hierarchy Service. From Inventory's perspective, a node is an external pointer.

When the Inventory API receives a POST /relations, it validates the UUID syntax of both entity IDs. It then resolves and checks permissions for an entity only when its type is asset, regardless of whether it is the subject or the object. It does not verify that an external node exists, because a node references an external scope that might not have a physical or digital representation. Consequently:

  • A missing asset subject or object returns an error.
  • The caller must have read and write permissions for every referenced asset in that asset's access group.
  • A node ID is not checked for existence by Inventory; the external service owns that validation.

Origins and Creation

Relations have two origins: IAH can create them automatically from system events, or users can create them explicitly through the Inventory API.

System-Managed Relations

IAH creates or updates these relations automatically. The source identifies the process that created the relation. The source value identifies the specific source instance where applicable.

Predicate Meaning Subject Object Source type Source value
found_by_gateway An asset was found by a gateway. Discovered asset Gateway asset discovery Discovery source, for example siemens.cdm.dcd.sat
found_by_asset_link An asset was found by an Asset Link. Discovered asset Asset Link asset discovery Discovery source, for example siemens.cdm.dcd.sat
is_installed_on An Asset Link is installed on a gateway. Installation does not imply that the Asset Link is currently running. Asset Link asset Gateway asset assetlinkdiscovery siemens.cdm.gateway.assetlinkdiscovery
is_running_on An Asset Link is currently running on a gateway. This represents the current runtime state. Asset Link asset Gateway asset assetlinkdiscovery siemens.cdm.gateway.assetlinkdiscovery

The direction is significant. For example: PLC -> found_by_gateway -> Gateway means that the gateway found the PLC, while: Asset Link -> is_running_on -> Gateway means that the Asset Link is currently running on the gateway.

System relations may be deleted manually, but the system will recreate or update them when the related event occurs again.

User-Managed Relations

Create a relation with the Inventory API's POST /relations endpoint. The request must contain subject, predicate, and object. The optional source.value identifies the source; the source type is set to manual by Inventory to distinguish between manual and system-managed relations. isBidirectional: true also creates the reverse relation atomically.

Creating a relation requires the global permission glb_inventory_write_relation_data. Every referenced asset also requires inventory_read_asset_data and inventory_write_asset_data in its access group. Nodes have no Inventory access group check.

Recommended predicates such as is_module_of and is_periphery_of can be used for user-managed relations between an asset and related child items. These predicates retain their meaning across contexts. Examples are:

Predicate Meaning Example origin
is_module_of An asset is a submodule of another asset. SAT Asset Link or Proneta Service
is_periphery_of An asset is a periphery of another asset. SAT Asset Link or Proneta Service

Example asset-to-asset relation:

{
  "data": {
    "subject": { "type": "asset", "id": "<asset-id>" },
    "predicate": "is_connected_to",
    "object": { "type": "asset", "id": "<other-asset-id>" },
    "isBidirectional": true
  }
}

Example asset assignment to a context node:

{
  "data": {
    "subject": { "type": "asset", "id": "<asset-id>" },
    "predicate": "is_assigned_to_node",
    "object": { "type": "node", "id": "<node-id>" }
  }
}

Creating the same relation again updates its timestamp instead of creating a duplicate. Relations cannot be edited; delete and recreate an incorrect relation.

Create Relations with an Asset

The Inventory API can also create or update user-managed asset-to-asset relations when creating or updating an asset with POST /assets. Add an asset_relations array to data.asset and identify the related asset by its asset identifiers. The asset relation also supports is_bidirectional:

The following is a minimal v2 example asset payload. Both the current asset and the related asset use the CustomIdentifier name ExternalSystemID; the related asset must already exist in the inventory.

{
  "data": {
    "asset": {
      "functional_object_type": "Device",
      "functional_object_schema_url": "https://industrial-assets.io/schemas/iah/base-schema/released/v1/iah-base.json",
      "asset_identifiers": [
        {
          "asset_identifier_type": "CustomIdentifier",
          "name": "ExternalSystemID",
          "value": "asset-a"
        }
      ],
      "asset_relations": [
        {
          "predicate": "is_part_of",
          "related_asset": {
            "asset_identifiers": [
              {
                "asset_identifier_type": "CustomIdentifier",
                "name": "ExternalSystemID",
                "value": "asset-b"
              }
            ]
          },
          "relational_role_of_related_asset": "object",
          "is_bidirectional": false
        }
      ]
    }
  }
}

The asset in data.asset takes the opposite role. With relational_role_of_related_asset: "object", the relation is:

asset-a -> is_part_of -> asset-b

Set the value to subject to reverse the direction. The relation source is taken from data.meta.source; unlike POST /relations, it is not always set to manual. This feature creates asset-to-asset relations only; use the Inventory API's POST /relations endpoint for relations involving nodes. The relation is skipped if the related asset cannot be found or the caller lacks the required permissions. The asset creation or update can still succeed.

Consumer Side: Read and Filter Relations

Use GET /relations with filter, limit, and offset. It requires the global permission glb_inventory_read_relation_data. Filters use OData-inspired syntax and must be URL encoded in the request. The following readable filters are shown before encoding:

Filter Purpose
subject.type eq 'asset' and subject.id eq '<asset-id>' Relations starting at an asset
object.type eq 'asset' and object.id eq '<asset-id>' Relations ending at an asset
object.type eq 'node' and predicate eq 'is_assigned_to_node' Asset assignments to nodes
predicate eq 'found_by_gateway' Assets found by gateways
object.id eq '<gateway-id>' and predicate eq 'is_installed_on' Asset Links installed on a gateway
object.id eq '<gateway-id>' and predicate eq 'is_running_on' Asset Links currently running on a gateway
source.type eq 'manual' and predicate eq 'is_assigned_to_node' Manual node assignments

For example, find the gateway that found an asset. The human-readable query is:

GET /relations?filter=subject.type eq 'asset' and subject.id eq '<asset-id>' and predicate eq 'found_by_gateway'

Use its URL-encoded form in the request:

GET /relations?filter=subject.type%20eq%20%27asset%27%20and%20subject.id%20eq%20%27<asset-id>%27%20and%20predicate%20eq%20%27found_by_gateway%27

Use GET /assets?relationsFilter=... to return assets matching their relations. An asset ID used as subject.id returns matching relation objects; an asset ID used as object.id returns matching relation subjects. Without a concrete anchor, matching asset sides are returned, while node sides are not returned as assets.

For example, retrieve readable assets assigned to nodes. The human-readable query is:

GET /assets?relationsFilter=object.type eq 'node' and predicate eq 'is_assigned_to_node'

Use its URL-encoded form in the request:

GET /assets?relationsFilter=object.type%20eq%20%27node%27%20and%20predicate%20eq%20%27is_assigned_to_node%27

Deletion

Deleting an asset automatically deletes every relation in which that asset is the subject or object. This also removes relations that point to the asset from an external entity.

Deleting an external node does not delete its relations in Inventory, because Inventory does not know about the actual entity behind a node's UUID.

Additionally, delete relations explicitly with two endpoints:

  • DELETE /relations deletes up to 50 relations by ID.
  • DELETE /relations/query-results deletes matching relations, up to 100 per request.

Both endpoints require the global permissions glb_inventory_read_relation_data and glb_inventory_write_relation_data.

The query-result endpoint requires a narrowing filter, such as the following human-readable query:

relationsFilter=object.type eq 'node' and object.id eq '<node-id>' and predicate eq 'is_assigned_to_node'

Use its URL-encoded form in the request:

relationsFilter=object.type%20eq%20%27node%27%20and%20object.id%20eq%20%27<node-id>%27%20and%20predicate%20eq%20%27is_assigned_to_node%27

Repeat the request while the response reports more deletable relations than deleted relations.

Any questions left?

Ask the community