Find Twins in the Digital Twin Registry

Twins can be queried at the /registry-api/v2/twins endpoint. This endpoint supports filtering and paging in order to give the client control about how many and which resources it wants to retrieve.

Filtering reduces the amount of retrieved entities by given criteria, paging returns a subset of the results. With sorting, the retrieved results are returned in a predefined order.

Filtering

The Digital Twin Registry API supports filtering via RQL (Resource Query Language)externallink 20.

Filtering of results is specified by the value of the query parameter filter.

The general form of the query string looks as follows:

filter={filter expression}&option=sort((+|-)field)

If there is an issue processing the filter expression, the response will provide details on the error.

Operators

The value for {filter expression} is an expression that uses one of the following operators.

  • Comparison operators:

    • eq

    • in

    • ne

    • gt

    • ge

    • lt

    • le

    • like

    • likeIgnoreCase

  • Logical operators:

    • and

    • or

    • not

The following examples illustrate how an RQL {filter expression} could look like.

Return Twins that have a description containing "my device":

likeIgnoreCase(description,"*my device*")

Return Twins that carry a label named "A":

eq(labels.name,"A")

Return Twins of twinKind "INSTANCE":

eq(twinKind,"INSTANCE")

Return Twins that have a twinCategory of either "Machine" or "Device":

in(twinCategory,"Machine","Device")

Return Twins with a given twinCategory and at least one label with name "Floor1":

and(eq(twinCategory,"Printer"), eq(labels.name,"Floor1"))

Supported attributes

Table 1. Supported attributes
Starting root Attributes

Twin

id, twinType, twinCategory, twinKind, description, aspects, localIdentifiers, labels, groups (are Digital Twin Groups - shortcut for groupMemberships.group)

Local identifier

id, digitalTwin, hash, valueSet, localIdentifierKeyPairs.<key>

Label

id, name, digitalTwin

Digital Twin Group

id, name

Aspect

id, modelReference, digitalTwin, httpEndpoints, mqttEndpoints

Aspect Model reference

urn

Aspect HTTP endpoint

id, aspect, twin

Aspect MQTT endpoint

id, aspect

Aspect template

id, name, modelReference, httpEndpointTemplates, mqttEndpointTemplates, groups (are Digital Twin Groups - shortcut for templateGroupMemberships.digitalTwinGroup)

Model reference template

id, urn

HTTP endpoint template

id, aspectTemplate

MQTT endpoint template

id, aspectTemplate

Group statistic

id

Each RQL filtering expression displays which is the starting root.

The following attributes work with the respective operators:

Attributes Operators

id

All comparison operators except like and likeIgnoreCase (eq, in, ne, gt, ge, lt, le)

Label.name

eq, in, ne

DigitalTwin.twinType, DigitalTwin.twinCategory, DigitalTwin.description, DigitalTwin.twinKind, LocalIdentifier.hash, LocalIdentifier.valueSet, LocalIdentifier,localIdentifierKeyPairs.<key>, Aspect.modelReference.urn, AspectTemplate.modelReference.urn

All comparison operators (eq, in, ne, gt, ge, lt, le, like, IgnoreCase)

Relation attributes —  used to cascade from one starting root to another. They are named like the starting roots, to which they lead, e.g. aspect, twin, aspectTemplate.

They do not work with operators on their own, but combined with attributes that do, they form queries that use the supported operators for those attributes.

For example:

Starting from aspect:
eq(aspect.digitalTwin.labels.name, "Floor1")

Starting from digitalTwin:
in(digitalTwin.aspects.httpEndpoints.id, 1, 2)
eq(digitalTwin.aspects.modelReference.urn, "urn:samm:com.bosch.nexeed.digitaltwin:1.1.0#Aspect")

Starting from aspectTemplate:
like(aspectTemplate.groups.name, "Printer*")

If additional attributes are required, please contact us at support.semantic-stack@bosch.com.

Paging

A number of API endpoints provide cursor pagination.

Cursor pagination

The cursor pagination methodology divides the entities of a larger dataset, based on various criteria.

  • This type of pagination is broadly intended for continuous pagination and therefore lacks the option to select a specific page index, in favor of basing the pagination on a specific cursor, provided by the back-end in the first response (assuming the dataset has more than one page).

  • In cursor pagination, once the client obtains the first page and a cursor for the next one, it can send the next request with that cursor as an argument, in order to fetch the next page.

  • Cursor pagination uses logarithmic solutions to scroll through a sorted dataset, which tends to perform increasingly better than offset pagination the larger the dataset and the closer to its end data is being fetched.

  • In other words, cursor pagination is preferable in the vast majority of use cases

One criterion to cursor pagination methodology is the size of the page, expressed as:

  • the limit argument expressed as a query parameter in non-bulk APIs using cursor pagination (e.g., ?limit=50)

  • the limit argument expressed in the request body for bulk APIs using cursor pagination (e.g., "limit": 50)

There is a small difference between the Digital Twin Registry API and the Bulk API:

  • the bulk API endpoints feature both offset and cursor pagination based on the request parametrization: if RQL limit(x,y) is used, offset pagination will be used - conversely, if the limit and cursor arguments are used in the request body, cursor pagination will be used

  • the non-bulk API endpoints featuring cursor pagination for the v2 API

Finally, the page size defaults are featured similarly, but not identically for the two methodologies:

Table 2. Page size defaults and maximum values
Operation type Cursor-based Default page size Maximum page size

Bulk

Yes

Relevant bulk max quota for that endpoint

Relevant bulk max quota for that endpoint

Bulk

No

No default: desired page size must be specified in RQL limit(x, y) - where y is the desired page size and is validated against max quota

Relevant bulk max quota for that endpoint

Non-bulk

Yes

500

500

  • In all cases, when a desired page size exceeds the maximum page size or quota, the request will fail with an error

  • For specific information about the bulk API quotas, please refer to the API documentationexternallink 20

Cursor pagination interaction lifecycle

This section summarizes the typical exchanges between client and server when cursor pagination is involved. The two small sections above underline the similarities and small differences between bulk and non-bulk APIs in that process.

Bulk APIs

  1. The client requests data via a cursor-paginating endpoint, optionally parameterizing the request with:

    • a `limit` within the supported bulk quota (defaults to `500` elements if absent)

    • an RQL filter query (defaults to "all elements of that type" if absent)

  2. The server responds with the first page of elements.

    • If more elements are present in the dataset, the response body will contain a nextCursor value, expressed as a string.

  3. If the server response contains a nextCursor value, the client copies it as cursor in the query parameters of the next request

  4. In turn, the server provides a page of elements after that cursor, and again the nextCursor value in the next response, if any more pages are available.

  5. This exchange iterates ad lib. until the server stops providing a nextCursor value in the response.

  6. Once no nextCursor value is available to the client, it "knows" it has reached the end of the desired dataset, and can stop sending further requests.

Non-bulk APIs

  1. The client requests data via a cursor-paginating endpoint, optionally parameterizing the request with:

    • a limit value up to, and defaulting to 500 elements

    • an RQL filter query (defaults to "all elements of that type" if absent)

  2. The server responds with the first page of elements.

    • If more elements are present in the dataset, the response body will contain a nextCursor value, expressed as a string.

  3. If the server response contains a nextCursor value, the client copies it as cursor in the query parameters of the next request

  4. In turn, the server provides a page of elements after that cursor, and again the nextCursor value in the next response, if any more pages are available.

  5. This exchange iterates ad lib. until the server stops providing a nextCursor value in the response.

  6. Once no nextCursor value is available to the client, it "knows" it has reached the end of the desired dataset, and can stop sending further requests.

Notes

  • In both API types (bulk and non-bulk), DELETE operations do not really require the client to use the cursor at all except to infer the dataset is empty, once the server stops sending the nextCursor

  • The client may send a cursor value, the server may respond with nextCursor value

Sorting

With user-defined sorting, the client is responsible for preventing an arbitrary order of the retrieved results.

User-defined sorting can be achieved by using the RQL options operator with the keyword sort.

Two different use cases exist:

  • Use case 1: The client provides sorting information via the sort keyword.

    • Example: option=sort(-id)

    • Sort direction for a property can be given with:

      • + for ascending order

      • - for descending order

  • Use case 2: The client does not provide any sorting information.

    • The results will be sorted by default sorting.

    • Default sorting happens by ID and in ascending order, which implicitly also yields an ascending order by creation time.

If sorting is used with a cursor, the sort expression must NOT change from the previous request. Otherwise incorrect results or an error may occur.

Note that:

  • User-defined sorting may result in longer response times.

  • Even if the client provides sorting information, arbitrary ordering can still occur if parallel accesses (deleting or adding elements) take place.

  • If it is important that all elements are returned, user-defined sorting cannot be used.

Currently, only reviewed attributes from the root entity can be used. If a desired attribute is not available, please contact us.

Select

The select operator trims each response down to the set of attributes defined in the arguments of the query. User-defined select can be requested via RQL for a root entity. All child entities will then not be loaded.

This means that response times can be significantly improved if only the requested information is returned.

Example:

Load only twin IDs (select) and do that only for twins that have the label blue assigned (filter).

select=id&filter=eq(labels.name, "blue")