API Documentation

This documentation complements the swagger documentation of our API and contains addition information and background to the endpoints.

API Versioning

twinsphere supports API versions v3, v3.0, and v3.1. All three route to the same implementation and are functionally identical. Clients should prefer v3 in their base URL — this always points to the latest supported minor version.


Discovery

Implicit Discovery

We have introduced a proprietary comfort feature that allows asset links to be retrieved without or in addition to an existing "standard" discovery. Our Implicit Discovery works directly on the tenant's existing asset administration shell repository data and creates shell descriptors on-the-fly when called.

The advantage of this feature is that all shells of your shell repository are hereby discoverable by default - without the need of explicitly entering lookup data into a discovery. The limitation of it is that only those "local shell" are discoverable and the quantity cannot be restricted or extended to shells of further (external) repositories. For distributed scenarios you need to use a standard discovery.

The endpoints of our implicit discovery feature match IDTA's read service profile SSP-002 of the Discovery Service Specification and are located at the top level of our api path:

https://{twinsphereTenantURL}/api/{api_version}/lookup/shellsByAssetLink

Standard ("Explicit") Discovery

Of course twinsphere also offers the official discovery service as specified. It is even possible to have multiple instances of discoveries with different sets of shell descriptors within one tenants.

All endpoints of our discovery feature match IDTA's full service profile SSP-001 of the Discovery Service Specification and are separated under a freely definable instance name:

https://{twinsphereTenantURL}/{instanceName}/api/{api_version}/lookup/shellsByAssetLink

In order to obtain a new discovery instance please contact our support team.

To search for shells by asset identifiers, send a POST request with a list of SpecificAssetId objects (name and value) in the request body. To search by global asset ID, set name to globalAssetId (per Constraint AASd-116). Pagination is supported via limit and cursor query parameters.

Deprecation of GET /lookup/shells

The previous GET /lookup/shells endpoint (query-parameter based) is deprecated and will be removed in a future release. Please migrate to POST /lookup/shellsByAssetLink.


Shell Registry

Implicit Shell Registry

We have introduced a proprietary comfort feature that allows shell descriptors to be retrieved without or in addition to an existing "standard" shell registry. Our Implicit Shell Registry works directly on the tenant's existing asset administration shell repository data and creates basic shell descriptors on-the-fly when called.

The advantage of this feature is that all shells of your shell repository are hereby registered by default - without the need of explicitly entering descriptors into a registry. The limitation of it is that only local shells are retrievable and their quantity cannot be restricted or extended to shells of further (external) repositories. For distributed scenarios you need to use a standard registry.

You might think: What a pointless feature - after all, you are asking a twinsphere tenant where a shell is located that is hosted by this very tenant. So the answer you'll get will always be "The shell is here with me!". However, the advantage of this approach lies in simple client-centered use cases, where clients are able to follow the standard call procedure (Discovery => Registry => Shell => Submodel) without you having to deal with shell registrations.

The endpoints of our Implicit Shell Registry feature correspond to the IDTA read service profile SSP-002 of the Discovery Service Specification and are located at the top level of our api path:

https://{twinsphereTenantURL}/api/{api_version}/shell-descriptors

Standard ("Explicit") Shell Registry

Of course twinsphere also offers the official Asset Administration Shell Registry service as specified. It is even possible to have multiple instances of registries with different sets of shell descriptors within one tenants.

All endpoints of our registry feature correspond to the IDTA full service profile SSP-001 of the Asset Administration Shell Registry Service Specification and are separated under a freely definable instance name:

https://{twinsphereTenantURL}/{instanceName}/api/{api_version}/shell-descriptors

In order to obtain such a new registry instance please contact our support team.


Submodel Registry

Implicit Submodel Registry

We have introduced a proprietary comfort feature that allows submodel descriptors to be retrieved without or in addition to an existing "standard" submodel registry. Our Implicit Submodel Registry works directly on the tenant's existing submodel repository data and creates basic submodel descriptors on-the-fly when called.

For the advantage and limitations of this feature are analog to those of our Implicit Shell Registry. Please refer there.

The endpoints of our Implicit Submodel Registry feature correspond to the IDTA read service profile SSP-002 of the Discovery Service Specification and are located at the top level of our api path:

https://{twinsphereTenantURL}/api/{api_version}/submodel-descriptors

Standard ("Explicit") Submodel Registry

Not yet available, coming soon...


Shell Repository

Enhanced GET /shells

We have introduced an optional extra query parameter "assetKind" for filtering shells according to their asset kind. Presently, the available asset kinds include:

  • Instance
  • Type

You can utilize this parameter within your existing /shells calls to filter based on the asset kind. If this parameter isn't provided, all shells, regardless of their asset kind, will be returned.

Due to its optional nature, the parameter is a non-breaking extension of the standard.


Submodel Repository

Enhanced GET /submodels

We have introduced an optional extra query parameter "kind" for filtering submodels according to their kind. Presently, the available kinds include:

  • Instance
  • Template

You can utilize this parameter within your existing /submodels calls to filter based on the kind. If this parameter isn't provided, all submodels, regardless of their kind, will be returned.

Due to its optional nature, the parameter is a non-breaking extension of the standard.


Concept Description Repository

Available and works :) Nothing special beyond the defined standard to document here.


File Repository

twinsphere is self-sufficiently capable of hosting and managing files and documents to be referenced from any submodel within your tenant.

twinsphere File Paths

The key concept behind the file repository are so called 'twinsphere file paths' (tfp). This feature enables clients to reuse and share files between shells and submodels, eliminating the need to upload the same files multiple times.

A tfp follows a fixed format/pattern, for example: file:/dHdpbnNwaGVyZS1maWxl/cp/18ef5516-618b-4a97-ac4b-d47f5d5ee823/thumbnail.jpg. It follows the file URI scheme (as requested by the specifications in advance to v3.1) and consists of a static part, the tenant name, a unique documentId, and a file name.

This tfp cannot be generated by the client. It is created by twinsphere when uploading a file to the repository (see Usage below) and should be (re)used in submodels when referencing a file resources.

Please notice

A tfp is only valid in the context of a single tenant and is not reachable from outside this context.

Usage of File API

twinsphere's file repository is accessible via the "files" endpoints at https://{tenant_url}/api/v3.0/files…

Once a file has been uploaded to the file repository it can be referenced by shells (as thumbnail) or submodels (as attachment) using its corresponding tfp.

We recommend the following procedure:

  1. POST /files to upload a document, tfp is returned
  2. Remember the tfp and use it as File.value in submodels as required

Please notice

There is currently no way to search for a tfp in the file repository, as no metadata about the file is stored apart from the file name - and this may not be unique in the repository.

Replacing an existing file by PUT /files will replace the files content while keeping its filename and tfp. So all reference stay valid and now point to the new content.

To remove a file physically from the repository, you have to use the DELETE /files interface. Please be aware that there is no check for existing references, so you may create invalid reference by deleting a file.

You can attach meta information to a twinsphere file at https://{tenant_url}/sphere/api/v1.0/files/{tfp}/metadata endpoint.

{
  "displayName": "Document Display Name",
  "classification": "01-01",
  "attributes": {
    { "key": "external-id", "value": "1" },
    { "key": "country", "value": "germany" },
    { "key": "priority", "value": "important" }
  }
}
  • displayName: Custom name for the file.
  • classification: Must be a valid class ID as defined in VDI 2770.
  • Attributes: Custom key–value metadata.
  • Attributes Limit: Up to 50 attributes per file.
  • Attribute Size: Keys and values can each be up to 2048 characters.

Adaption of standard operations

The introduction of our file repository made it necessary to adapt the behavior of standard operations working with files:

  • DELETE /attachment and DELETE /thumbnail
    • Only removes file references from SM/AAS.
    • Deletion of file itself is only possible via DELETE /files (see above)
  • PUT /attachment and PUT /thumbnail
    • Uploads new file to repo and updates this single reference with new twinsphere File Path.
    • Former file stays unchanged, still exists on file repo and its twinsphere File Path is still valid.
    • All other references to the former file stay untouched.
  • GET /serialization for AASX packages
    • Packs all referenced files of resolvable tfps (file exists in tenant's file repository) and ignores invalid twinsphere File Paths (non-existing files)

Implications of this adapted behavior:

  • If you (re-)upload a thumbnail or attachment via standard endpoints all referencing shells/submodels which used the previous file must be updated as well to use the updated file.
  • A better way to update all references of an old file is to reupload the file via PUT /files. Its filename cannot be changed in this way though.
  • If you update the thumbnail or attachment file value/path entry in the metamodel to something that does not resemble a twinsphere file path (e.g., file:/aasx/file/thumbnail.jpg), the download of the file will no longer work.
  • If you update references to a file in a single submodel element (with "PUT attachment" or "PUT thumbnail"), all other references will still be valid but are not updated and point to the former file.

Import

An AASX package can be uploaded via the import function. Currently, only one shell is supported. The import process is not transactional, so the shell, submodels, and files may be partially created even in the event of an error. It is important to note that an import always overwrites existing data.

A package is considered valid if:

  • Successful validation
  • Exactly one shell
  • Other elements are optional

If a thumbnail is required, an asset thumbnail must be provided. Package thumbnails are ignored.

Currently, there is a limitation in "[Content_Types].xml". All paths are case-sensitive and must exactly match the folder path within the package.


Serialization

/serialization

The serialization endpoint returns an environment that contains specified asset administration shells, submodels and concept descriptions (see part 2 of the AAS specification ⧉).

The desired return type can be specified using the "Accept" header parameter of the request. Valid return types include

  • application/xml (default),
  • application/json, and
  • application/asset-administration-shell-package+json.

Packages currently contain the environment as JSON – XML coming soon.

Note

If you use the serialization in the Swagger UI there is one problem, you cannot set the accept header in the UI. You have to go down to the Responses section and there, in the 200 section, choose the accept header value from the drop down and send the request. Now you will get your correct serialized shell.

As mentioned in this issue on github ⧉, this is a Swagger UI / OpenApi problem and can not be fixed.

As mentioned in this issue on github ⧉, Swagger UI produces an corrupt File on using the Accept Header "application/asset-administration-shell-package+json". Using the API direct with for example Postman or CURL gives the correct File.

File Inclusion in AASX Packages

When using the application/asset-administration-shell-package+json format, files referenced within the selected shells and submodels are automatically included in the package. There is no dedicated parameter to explicitly include or exclude files — inclusion is determined by the content of the selected AAS and submodels.

Which files are included

  • Submodel File elements — All File submodel elements within the selected submodels that have a valid twinsphere file path are resolved and included under /aasx/files/ in the package.
  • Default thumbnail — The default thumbnail from the first selected shell's AssetInformation is included if it has a valid twinsphere file path.
  • File elements with a twinsphere file path (e.g., file:/dHdpbnNwaGVyZS1maWxl/cp/18ef5516-618b-4a97-ac4b-d47f5d5ee823/image.jpg) are downloaded from blob storage and packed into the AASX.
  • File elements with an external URL (e.g., https://example.com/file.pdf) are left as-is in the serialized environment metadata. The referenced file is not downloaded or included in the package.
  • File elements with invalid or non-existing twinsphere file paths are silently skipped.

Access-restricted files (ABAC)

When ABAC security is enabled, each file's security attributes are checked before inclusion. If the requesting user does not have sufficient permissions for a specific file:

  • The original file is not included in the package.
  • A placeholder file named {original-filename}.access-restricted.txt is included instead.
  • The file element's value in the serialized environment points to the placeholder, and its contentType is set to text/plain.

Users with a role-based (RBAC) grant bypass file-level ABAC checks and receive all files.

Serialization Modifiers

Level Modifier

Not yet available, coming soon...

Content Modifier

You can use special content modifiers to get special serializations of the requested data, e.g. only the metadata of an object.

Metadata ($metadata)

From IDTA Spec Part 2: "Only metadata of an element or child elements is returned; the value is not."

This is implemented for the following endpoints:

  • GET /submodel/{submodelIdentifier}/$metadata

Let us give you an example for this endpoint with the nameplate submodel.

{
  "idShort": "Nameplate",
  "id": "https://company.com/ids/sm/nameplate",
  "kind": "Instance",
  "semanticId": {
    "type": "ExternalReference",
    "keys": [
      {
        "type": "GlobalReference",
        "value": "https://admin-shell.io/zvei/nameplate/2/0/Nameplate"
      }
    ]
  },
  "modelType": "Submodel"
}
Value ($value)

Not yet available, coming soon...

Reference ($reference)

This gives you the Reference object of an object or a list of reference objects on endpoints which works with many objects. For example the shellIds of all shells in your repository.

From IDTA Spec Part 2: "Only applicable to Referables. Only the reference to the found element is returned; potential child elements are ignored."

Actually it is not implemented for the for the Asset Administration Shell and Submodel endpoints

  • /shells/{aasIdentifier}/$reference
  • /shells/{aasIdentifier}/submodels/{submodelIdentifier}/$reference
  • /shells/{aasIdentifier}/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/$reference
Path ($path)

From IDTA Spec Part 2: "Returns the idShort of the requested element and a list of idShort paths to child elements if the requested element is a Submodel, a SubmodelElementCollection, a SubmodelElementList, a AnnotatedRelationshipElement, or an Entity."

This is implemented for the following endpoints:

  • GET /submodel/{submodelIdentifier}/$path

The return value for our Nameplate Submodel would be:

[
  "URIOfTheProduct",
  "ManufacturerName",
  "ManufacturerProductDesignation",
  "ContactInformation",
  "ContactInformation.Company",
  "ContactInformation.CityTown",
  "ContactInformation.Phone",
  "ContactInformation.Phone.TelephoneNumber",
  "ContactInformation.Phone.TypeOfTelephone",
  "ContactInformation.Fax",
  "ContactInformation.Fax.FaxNumber",
  "ContactInformation.Fax.TypeOfFaxNumber",
  "ContactInformation.Street",
  "ContactInformation.Zipcode",
  "ContactInformation.AddressOfAdditionalLink",
  "ManufacturerProductRoot",
  "ManufacturerProductFamily",
  "OrderCodeOfManufacturer",
  "ProductArticleNumberOfManufacturer",
  "Markings",
  "Markings.Marking01",
  "Markings.Marking01.MarkingName",
  "Markings.Marking02",
  "Markings.Marking02.MarkingName",
  "Markings.Marking03",
  "Markings.Marking03.MarkingName"
]

Extent Modifier

Only applicable to BLOB elements.

The "extent" parameter controls whether embedded data is returned in base64 format or not. By default, if the parameter is not provided, no embedded data is returned. The possible values are "WithBLOBValue" and "WithoutBLOBValue". The default is "WithoutBlobValue".

There are only a few submodels that have BLOBs inside. But we can give you a generally example how the result with this modifier will look like. The name of the BLOB is "Library".

Please note that this modifier is given as a query parameter on the endpoints.

This is implemented for the following endpoints:

  • GET /submodel/{submodelIdentifier}?extent=

WithoutBLOBValue

{
    "Library": {
        "contentType": "application/octet-stream"
    }
}

WithBLOBValue

{
    "Library": {
        "contentType": "application/octet-stream"
        "value": "VGhpcyBpcyBteSBibG9i"
    }
}

Query Language

twinsphere implements the AAS Query Language as defined in the IDTA-01002-3 v3.1.1 specification. This feature allows you to query shells and submodels using structured JSON queries with support for filtering by any combination of shell, submodel, and submodel element attributes.

Endpoints

Query Submodels

POST /api/v3.1/query/submodels

Returns submodels matching the query condition.

Query Shells

POST /api/v3.1/query/shells

Returns asset administration shells matching the query condition.

Both endpoints accept the same query language syntax and support the same operators.

Note

The query language endpoints currently require role-based authorization. ABAC (Attribute-Based Access Control) is not yet supported for query endpoints — all authenticated users with the appropriate role can query all data in the tenant. If you rely on ABAC to restrict access to specific shells or submodels, be aware that the query endpoints currently bypass those restrictions. ABAC support for query endpoints will be added in a future release.

Query Parameters

Parameter Type Required Description
limit int No Maximum number of results per page
cursor string No Pagination cursor from a previous response

Request Body

The request body is a JSON object with the following structure:

{
  "$condition": <expression>,
  "$select": "id"
}
Property Type Required Description
$condition expression Yes The filter expression (see operators below)
$select string No Set to "id" to return only identifiers

When $select is omitted, the full shell or submodel payload is returned.

Response

{
  "result": [ ... ],
  "paging_metadata": {
    "cursor": "<next-cursor-or-null>",
    "resultType": "Submodel"
  }
}

The resultType is one of "Submodel", "AssetAdministrationShell", or "Identifier" (when using $select: "id"). If cursor is null, there are no more results.

Field References

Field references identify the attribute to query. The format is:

$<root>#<attribute>

Where <root> is $sm (submodel), $sme (submodel element), or $aas (shell).

Submodel Fields ($sm)

Field reference Description
$sm#id Submodel identifier
$sm#idShort Submodel short identifier
$sm#semanticId Semantic ID (shortcut for semanticId.keys[0].value)
$sm#semanticId.type Semantic ID reference type
$sm#semanticId.keys[N].type Type of the Nth semantic ID key
$sm#semanticId.keys[N].value Value of the Nth semantic ID key
$sm#semanticId.keys[].type Type of any semantic ID key
$sm#semanticId.keys[].value Value of any semantic ID key

Submodel Element Fields ($sme)

Field reference Description
$sme#idShort Element short identifier
$sme#value Element value
$sme#valueType XSD value type (e.g. xs:string)
$sme#language Language tag (for MultiLanguageProperty)
$sme#semanticId Semantic ID (shortcut for semanticId.keys[0].value)
$sme#semanticId.type Semantic ID reference type
$sme#semanticId.keys[N].type Type of the Nth semantic ID key
$sme#semanticId.keys[N].value Value of the Nth semantic ID key
$sme#semanticId.keys[].type Type of any semantic ID key
$sme#semanticId.keys[].value Value of any semantic ID key

Submodel element fields can include an idShort path to target a specific element in the submodel tree:

$sme.Collection.Property#value

This targets the element at path Collection.Property within the submodel.

Shell Fields ($aas)

Field reference Description
$aas#id Shell identifier
$aas#idShort Shell short identifier
$aas#assetInformation.assetKind Asset kind (Instance or Type)
$aas#assetInformation.assetType Asset type
$aas#assetInformation.globalAssetId Global asset identifier
$aas#assetInformation.specificAssetIds[].name Specific asset ID name
$aas#assetInformation.specificAssetIds[].value Specific asset ID value
$aas#assetInformation.specificAssetIds[].externalSubjectId.type External subject ID reference type
$aas#assetInformation.specificAssetIds[N].name Name of the Nth specific asset ID
$aas#assetInformation.specificAssetIds[N].value Value of the Nth specific asset ID
$aas#submodels[].type Type of submodel reference
$aas#submodels[].keys[].type Type of reference keys
$aas#submodels[].keys[].value Value of reference keys

Where [N] is a zero-based index for positional access and [] matches any position (wildcard).

Operators

Comparison Operators

Comparison operators take exactly two operands (left and right):

{ "$eq": [<left>, <right>] }
Operator Description Notes
$eq Equals
$ne Not equals
$gt Greater than Not allowed on booleans
$lt Less than Not allowed on booleans
$ge Greater than or equal Not allowed on booleans
$le Less than or equal Not allowed on booleans
$contains String contains Case-sensitive
$starts-with String starts with Case-sensitive
$ends-with String ends with Case-sensitive
$regex Regular expression match POSIX regex, validated at parse time

Logical Operators

{ "$and": [<expr1>, <expr2>, ...] }

{ "$or": [<expr1>, <expr2>, ...] }

{ "$not": <expr> }

$and and $or require at least two elements. $not takes a single expression.

Match Operator

$match scopes multiple conditions to the same array element. This is essential when querying array-valued attributes like submodel elements or specific asset IDs, where you need conditions to apply to the same entry rather than across different entries.

{ "$match": [<comparison1>, <comparison2>, ...] }

Rules:

  • Requires at least one comparison
  • Cannot contain logical operators ($and, $or, $not) directly
  • All conditions inside a $match must reference the same array root

Operands

Literals

Syntax Type Example
{"$strVal": "text"} String {"$strVal": "urn:example"}
{"$numVal": 42.5} Numeric {"$numVal": 100}
{"$boolean": true} Boolean {"$boolean": false}
{"$dateTimeVal": "2024-01-15T00:00:00Z"} DateTime ISO 8601 format
{"$timeVal": "14:30:00"} Time {"$timeVal": "08:00:00"}
{"$hexVal": "16#FF0A"} Hex Must start with 16#

Type Cast Operators

Cast operators convert a field value to a different type. If the conversion fails, the comparison evaluates to "invalid" (no match) rather than producing an error.

Syntax Description
{"$strCast": <operand>} Cast to string
{"$numCast": <operand>} Cast to number
{"$boolCast": <operand>} Cast to boolean
{"$dateTimeCast": <operand>} Cast to DateTime
{"$timeCast": <operand>} Cast to time
{"$hexCast": <operand>} Cast to hex

DateTime Extraction Operators

Extract a component from a DateTime value for numeric comparison:

Syntax Description Range
{"$year": <operand>} Extract year
{"$month": <operand>} Extract month 1-12
{"$dayOfMonth": <operand>} Extract day of month 1-31
{"$dayOfWeek": <operand>} Extract day of week 1 (Mon) - 7 (Sun)

Examples

Find submodels by semantic ID

{
  "$condition": {
    "$eq": [
      {"$field": "$sm#semanticId"},
      {"$strVal": "https://admin-shell.io/zvei/nameplate/2/0/Nameplate"}
    ]
  }
}

Find submodels by idShort substring

{
  "$condition": {
    "$contains": [
      {"$field": "$sm#idShort"},
      {"$strVal": "Nameplate"}
    ]
  }
}

Combine conditions with AND

{
  "$condition": {
    "$and": [
      {
        "$eq": [
          {"$field": "$sm#semanticId"},
          {"$strVal": "https://admin-shell.io/zvei/nameplate/2/0/Nameplate"}
        ]
      },
      {
        "$contains": [
          {"$field": "$sm#idShort"},
          {"$strVal": "Nameplate"}
        ]
      }
    ]
  }
}

Find submodel elements with a specific value (using $match)

Use $match to ensure that the semantic ID and value conditions apply to the same submodel element:

{
  "$condition": {
    "$match": [
      {
        "$eq": [
          {"$field": "$sme#semanticId"},
          {"$strVal": "0173-1#02-AAO677#002"}
        ]
      },
      {
        "$eq": [
          {"$field": "$sme#value"},
          {"$strVal": "ACME Corp"}
        ]
      }
    ]
  }
}

Query shells by asset kind

{
  "$condition": {
    "$eq": [
      {"$field": "$aas#assetInformation.assetKind"},
      {"$strVal": "Instance"}
    ]
  }
}

Cross-entity query (shell + submodel attributes)

Find submodels that belong to shells of a specific asset kind:

{
  "$condition": {
    "$and": [
      {
        "$eq": [
          {"$field": "$aas#assetInformation.assetKind"},
          {"$strVal": "Instance"}
        ]
      },
      {
        "$eq": [
          {"$field": "$sm#semanticId"},
          {"$strVal": "https://admin-shell.io/zvei/nameplate/2/0/Nameplate"}
        ]
      }
    ]
  }
}

Return only identifiers

Add $select: "id" to receive only IDs instead of full payloads:

{
  "$condition": {
    "$eq": [
      {"$field": "$sm#semanticId"},
      {"$strVal": "https://admin-shell.io/zvei/nameplate/2/0/Nameplate"}
    ]
  },
  "$select": "id"
}

Error Handling

If the query is syntactically or semantically invalid, the endpoint returns HTTP 400 with an error message describing what is wrong.


Pagination

Some data can be retrieved page by page. For this, you can pass a limit and a cursor.

The limit determines the maximum size of a page, and the cursor specifies the position from which data retrieval starts. The result object of such a request contains the cursor value for the next request/page under paging_metadata.cursor.

Interface example:

public async Task<IActionResult> GetAllAssetAdministrationShells(
        [FromQuery] List<string>? assetIds = null,
        [FromQuery] string? idShort = null,
        [FromQuery] int? limit = null,
        [FromQuery] string? cursor = null,
        [FromQuery] Core.AssetKind? assetKind = null)

Result Model:

{
    "result": {},
    "paging_metadata": {
        "cursor": ""
    }
}