# LitmusEdge 4.0.x API Documentation/Digital Twins - LE, LEM, LUNS API Docs

## List Static Attributes

**POST** `{{edgeUrl}}/digital-twins`

# List Static Attributes

Lists the static (constant) attributes attached to a model, or to a specific instance via `InstanceID`. Static attributes are typed key/value pairs that do **not** carry live data -- use them for things like `Manufacturer`, `Serial`, `InstallDate`.

When `InstanceID` is `null`, the result is the **model-level** definitions (the schema). When `InstanceID` is set, the result includes per-instance overrides on top.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
query ListStaticAttributes($input: ListStaticAttributeRequest!) {
  ListStaticAttributes(input: $input) {
    ID
    ModelID
    InstanceID
    Key
    Value
    CreatedAt
    UpdatedAt
  }
}
```

### Variables

| Field         | Type | Required | Description                                                                                       |
|---------------|------|----------|---------------------------------------------------------------------------------------------------|
| `ModelID`     | `ID` | Yes      | UUID of the model.                                                                                |
| `InstanceID`  | `ID` | No       | UUID of an instance to scope to. Pass `null` for the model-level definitions.                     |

```json
{
  "input": {
    "ModelID": "{{digital_twins_model_id}}",
    "InstanceID": null
  }
}
```

## Response

`200 OK` -- `application/json`

| Field                                | Type             | Description                                                                |
|--------------------------------------|------------------|----------------------------------------------------------------------------|
| `data.ListStaticAttributes`          | `[StaticAttr!]!` | Static attributes for the scope.                                           |
| `data.ListStaticAttributes[].ID`     | `ID`             | Attribute UUID. Used by `Update Static Attribute` and `Delete Static Attribute`. |
| `data.ListStaticAttributes[].ModelID`| `ID`             | Parent model.                                                              |
| `data.ListStaticAttributes[].InstanceID` | `ID`         | Parent instance (`00000000-...` for model-level definitions).              |
| `data.ListStaticAttributes[].Key`    | `String`         | Attribute name (e.g. `Manufacturer`).                                      |
| `data.ListStaticAttributes[].Value`  | `String`         | Attribute value.                                                           |
| `data.ListStaticAttributes[].CreatedAt`/`.UpdatedAt` | `Time` | ISO 8601 timestamps.                                                |

```json
{
  "data": {
    "ListStaticAttributes": [
      {
        "ID": "189EDB70-1029-4EBF-AB40-27E5423C12CE",
        "ModelID": "3A923971-5DFC-4633-B5AF-A6BF8C0B60E9",
        "InstanceID": "00000000-0000-0000-0000-000000000000",
        "Key": "Manufacturer",
        "Value": "API Manufacturer",
        "CreatedAt": "2023-08-16T00:03:50.174589529Z",
        "UpdatedAt": "2023-08-16T00:03:55.264377178Z"
      }
    ]
  }
}
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
query ListStaticAttributes($input:ListStaticAttributeRequest!) {
    ListStaticAttributes(input: $input) {
        ID
        ModelID
        InstanceID
        Key
        Value
        CreatedAt
        UpdatedAt
    }
}

```

**Variables**

```json
{
    "input": {
        "ModelID": "{{digital_twins_model_id}}", 
        "InstanceID": null
    }
}

```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "ListStaticAttributes": [
            {
                "ID": "189EDB70-1029-4EBF-AB40-27E5423C12CE",
                "ModelID": "3A923971-5DFC-4633-B5AF-A6BF8C0B60E9",
                "InstanceID": "00000000-0000-0000-0000-000000000000",
                "Key": "Manufacturer",
                "Value": "API Manufacturer",
                "CreatedAt": "2023-08-16T00:03:50.174589529Z",
                "UpdatedAt": "2023-08-16T00:03:55.264377178Z"
            }
        ]
    }
}
```

---

## Create Static Attribute

**POST** `{{edgeUrl}}/digital-twins`

# Create Static Attribute

Creates a new static attribute on a model. The attribute is part of the model's schema and is inherited by every instance unless overridden.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation CreateStaticAttribute($input: CreateStaticAttributeRequest!) {
  CreateStaticAttribute(input: $input) {
    ID
    ModelID
    InstanceID
    Key
    Value
    CreatedAt
    UpdatedAt
  }
}
```

### Variables

| Field     | Type     | Required | Description                                  |
|-----------|----------|----------|----------------------------------------------|
| `ModelID` | `ID`     | Yes      | UUID of the model to attach the attribute to.|
| `Key`     | `String` | Yes      | Attribute name. Must be unique within the model. |
| `Value`   | `String` | Yes      | Attribute value.                             |

```json
{
  "input": {
    "ModelID": "{{digital_twins_model_id}}",
    "Key": "Model",
    "Value": "New API Model"
  }
}
```

## Response

`200 OK` -- `application/json`. Same shape as one element of `List Static Attributes`.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation CreateStaticAttribute ($input: CreateStaticAttributeRequest!) {
    CreateStaticAttribute(input: $input) {
        ID
        ModelID
        InstanceID
        Key
        Value
        CreatedAt
        UpdatedAt
    }
}

```

**Variables**

```json
{
    "input": {
        "ModelID": "{{digital_twins_model_id}}",
        "Key": "Model",
        "Value": "New API Model"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "CreateStaticAttribute": {
            "ID": "63E4BD05-CE85-41CD-99A6-F69CCD171240",
            "ModelID": "3A923971-5DFC-4633-B5AF-A6BF8C0B60E9",
            "InstanceID": "00000000-0000-0000-0000-000000000000",
            "Key": "Model",
            "Value": "New API Model",
            "CreatedAt": "2023-08-16T00:13:38.224123983Z",
            "UpdatedAt": "2023-08-16T00:13:38.224126007Z"
        }
    }
}
```

---

## Update Static Attribute

**POST** `{{edgeUrl}}/digital-twins`

# Update Static Attribute

Updates the `Key` or `Value` of an existing static attribute. Pass the attribute's `ID` plus the new values. Identical schema is used to update both **model-level** definitions and **instance-level** overrides -- the endpoint behaves the same in either case.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation UpdateStaticAttribute($input: UpdateStaticAttributeRequest!) {
  UpdateStaticAttribute(input: $input) {
    ID
    ModelID
    InstanceID
    Key
    Value
    CreatedAt
    UpdatedAt
  }
}
```

### Variables

| Field     | Type     | Required | Description                                            |
|-----------|----------|----------|--------------------------------------------------------|
| `ID`      | `ID`     | Yes      | UUID of the static attribute.                          |
| `ModelID` | `ID`     | Yes      | UUID of the parent model.                              |
| `Key`     | `String` | Yes      | New attribute name. Must be unique within the model.   |
| `Value`   | `String` | Yes      | New attribute value.                                   |

```json
{
  "input": {
    "ID": "{{digital_twins_static_attribute_id}}",
    "ModelID": "{{digital_twins_model_id}}",
    "Key": "Model",
    "Value": "Updated Value API Model"
  }
}
```

## Response

`200 OK` -- `application/json`. Same shape as `List Static Attributes`, reflecting the post-update values and a refreshed `UpdatedAt`.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation UpdateStaticAttribute($input:UpdateStaticAttributeRequest!) {
    UpdateStaticAttribute(input: $input) {
        ID
        ModelID
        InstanceID
        Key
        Value
        CreatedAt
        UpdatedAt
    }
}

```

**Variables**

```json
{
    "input": {
        "ID": "{{digital_twins_static_attribute_id}}",
        "ModelID": "{{digital_twins_model_id}}",
        "Key": "Model",
        "Value": "Updated Value API Model"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "UpdateStaticAttribute": {
            "ID": "63E4BD05-CE85-41CD-99A6-F69CCD171240",
            "ModelID": "3A923971-5DFC-4633-B5AF-A6BF8C0B60E9",
            "InstanceID": "00000000-0000-0000-0000-000000000000",
            "Key": "Model",
            "Value": "Updated Value API Model",
            "CreatedAt": "2023-08-16T00:13:38.224123983Z",
            "UpdatedAt": "2023-08-16T00:23:12.872714078Z"
        }
    }
}
```

---

## Delete Static Attribute

**POST** `{{edgeUrl}}/digital-twins`

# Delete Static Attribute

Deletes one or more static attributes by ID. Despite the singular name, the mutation accepts a list -- pass any number of items in the `Items` array.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation DeleteStaticAttributes($input: DeleteStaticAttributesRequest!) {
  DeleteStaticAttributes(input: $input)
}
```

### Variables

| Field            | Type           | Required | Description                                            |
|------------------|----------------|----------|--------------------------------------------------------|
| `Items`          | `[Item!]!`     | Yes      | List of `{ID}` objects -- one per attribute to delete.  |
| `Items[].ID`     | `ID`           | Yes      | UUID of the static attribute.                          |

```json
{
  "input": {
    "Items": [
      { "ID": "{{digital_twins_static_attribute_id}}" }
    ]
  }
}
```

## Response

`200 OK` -- `application/json`. The mutation returns no data on success.

```json
{ "data": { "DeleteStaticAttributes": null } }
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation DeleteStaticAttributes($input: DeleteStaticAttributesRequest!) {
    DeleteStaticAttributes(input: $input)
}

```

**Variables**

```json
{
    "input": {
        "Items": [
            {
                "ID": "{{digital_twins_static_attribute_id}}"
            }
        ]
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "DeleteStaticAttributes": null
    }
}
```

---

## Upload an Image

**POST** `{{edgeUrl}}/digital-twins`

# Upload an Image

Uploads an image to associate with a digital twin model (e.g. a photo or diagram of the asset). Multipart `form-data` request following the [GraphQL multipart request spec](https://github.com/jaydenseric/graphql-multipart-request-spec).

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body (form-data parts)

| Form key      | Type    | Description                                                                                                |
|---------------|---------|------------------------------------------------------------------------------------------------------------|
| `operations`  | `text`  | GraphQL envelope describing the mutation, with `null` placeholder for the file.                            |
| `map`         | `text`  | JSON mapping form file keys to placeholder locations within `operations`.                                  |
| `1`           | `file`  | The image file (binary). The form key (`1`) must match what appears in `map`.                              |

### `operations` value

```json
{
  "operationName": "UploadImage",
  "variables": { "input": { "ObjectID": "{{digital_twins_model_id}}", "File": null } },
  "query": "mutation UploadImage($input: ImageRequest!) { UploadImage(input: $input) }"
}
```

### `map` value

```json
{ "1": ["variables.input.File"] }
```

The `ObjectID` is the model UUID; the same endpoint is reused on instances by passing an instance UUID.

## Response

`200 OK` -- `application/json`

| Field                | Type      | Description           |
|----------------------|-----------|-----------------------|
| `data.UploadImage`   | `Boolean` | `true` on success.    |

```json
{ "data": { "UploadImage": true } }
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

### Response

**Status**: 200 OK

```json
{
    "data": {
        "UploadImage": true
    }
}
```

---

## Delete an Image

**POST** `{{edgeUrl}}/digital-twins`

# Delete an Image

Removes the image previously uploaded by `Upload an Image` from a model (or instance). Idempotent -- deleting when there is no image is a no-op.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation DeleteImage($input: DeleteImageRequest!) {
  DeleteImage(input: $input)
}
```

### Variables

| Field       | Type | Required | Description                                                  |
|-------------|------|----------|--------------------------------------------------------------|
| `ObjectID`  | `ID` | Yes      | UUID of the model (or instance) whose image to delete.       |

```json
{
  "input": {
    "ObjectID": "{{digital_twins_model_id}}"
  }
}
```

## Response

`200 OK` -- `application/json`

| Field                | Type      | Description           |
|----------------------|-----------|-----------------------|
| `data.DeleteImage`   | `Boolean` | `true` on success.    |

```json
{ "data": { "DeleteImage": true } }
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation DeleteImage($input: DeleteImageRequest!) {
    DeleteImage(input: $input)
}

```

**Variables**

```json
{
    "input": {
        "ObjectID": "{{digital_twins_model_id}}"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "DeleteImage": true
    }
}
```

---

## Get Transformations

**POST** `{{edgeUrl}}/digital-twins`

# Get Transformations

Lists every transformation defined on a model. A **transformation** is a named JSON Schema that dynamic attributes can reference to validate or shape their payload before it is published.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
query ListTransformations($input: ListTransformationRequest!) {
  ListTransformations(input: $input) {
    ID
    Name
    Schema
  }
}
```

### Variables

| Field       | Type | Required | Description                                  |
|-------------|------|----------|----------------------------------------------|
| `ModelID`   | `ID` | Yes      | UUID of the model.                           |

```json
{
  "input": {
    "ModelID": "{{digital_twins_model_id}}"
  }
}
```

## Response

`200 OK` -- `application/json`

| Field                                  | Type                  | Description                                       |
|----------------------------------------|-----------------------|---------------------------------------------------|
| `data.ListTransformations`             | `[Transformation!]!`  | All transformations on the model.                 |
| `data.ListTransformations[].ID`        | `ID`                  | Transformation UUID.                              |
| `data.ListTransformations[].Name`      | `String`              | Display name.                                     |
| `data.ListTransformations[].Schema`    | `String`              | JSON Schema as a string. Parse client-side.       |

```json
{
  "data": {
    "ListTransformations": [
      {
        "ID": "ABF0E58E-2D3C-4E85-A42F-18CFE9D94C91",
        "Name": "apiTransformation",
        "Schema": "{\"key\": \"value\"}"
      }
    ]
  }
}
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
query ListTransformations ($input:ListTransformationRequest!){
    ListTransformations(input: $input) {
        ID
        Name
        Schema
    }
}

```

**Variables**

```json
{
    "input": {
        "ModelID": "{{digital_twins_model_id}}"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "ListTransformations": [
            {
                "ID": "ABF0E58E-2D3C-4E85-A42F-18CFE9D94C91",
                "Name": "apiTransformation",
                "Schema": "{\"key\": \"value\"}"
            },
            {
                "ID": "30D86400-D103-4C5C-95E4-AB6F02FB00EE",
                "Name": "Transformation",
                "Schema": "{\n  \"key\": \"value\"\n}"
            }
        ]
    }
}
```

---

## Create Transformation

**POST** `{{edgeUrl}}/digital-twins`

# Create Transformation

Creates a new transformation on a model. The `Schema` is a JSON Schema document represented as a string.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation CreateTransformation($input: CreateTransformationRequest!) {
  CreateTransformation(input: $input) {
    ID
    Name
    Schema
  }
}
```

### Variables

| Field       | Type     | Required | Description                                  |
|-------------|----------|----------|----------------------------------------------|
| `ModelID`   | `ID`     | Yes      | UUID of the parent model.                    |
| `Name`      | `String` | Yes      | Transformation name. Must be unique in the model. |
| `Schema`    | `String` | Yes      | JSON Schema as a string.                     |

```json
{
  "input": {
    "ModelID": "{{digital_twins_model_id}}",
    "Name": "apiTransformation",
    "Schema": "{\"key\": \"value\"}"
  }
}
```

## Response

`200 OK` -- `application/json`. Returns the new transformation.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation CreateTransformation($input: CreateTransformationRequest!) {
  CreateTransformation(input: $input) {
    ID
    Name
    Schema
  }
}
```

**Variables**

```json
{
  "input": {
    "ModelID": "{{digital_twins_model_id}}",
    "Name": "apiTransformation",
    "Schema": "{\"key\": \"value\"}"
  }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "CreateTransformation": {
            "ID": "ABF0E58E-2D3C-4E85-A42F-18CFE9D94C91",
            "Name": "apiTransformation",
            "Schema": "{\"key\": \"value\"}"
        }
    }
}
```

---

## Update Transformation

**POST** `{{edgeUrl}}/digital-twins`

# Update Transformation

Updates the `Name` and/or `Schema` of an existing transformation.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation UpdateTransformation($input: UpdateTransformationRequest!) {
  UpdateTransformation(input: $input) {
    ID
    Name
    Schema
  }
}
```

### Variables

| Field   | Type     | Required | Description                                                    |
|---------|----------|----------|----------------------------------------------------------------|
| `ID`    | `ID`     | Yes      | UUID of the transformation.                                    |
| `Name`  | `String` | Yes      | New transformation name.                                       |
| `Schema`| `String` | Yes      | New JSON Schema (as a string).                                 |

```json
{
  "input": {
    "ID": "{{digital_twins_transformation_id}}",
    "Name": "updatedTransformation",
    "Schema": "{\"updatedKey\": \"updatedValue\"}"
  }
}
```

## Response

`200 OK` -- `application/json`. Returns the updated transformation.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation UpdateTransformation ($input: UpdateTransformationRequest!) {
    UpdateTransformation(input: $input) {
        ID
        Name
        Schema
    }
}

```

**Variables**

```json
{
    "input": {
        "ID": "{{digital_twins_transformation_id}}",
        "Name": "updatedTransformation",
        "Schema": "{\"updatedKey\": \"updatedValue\"}"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "UpdateTransformation": {
            "ID": "ABF0E58E-2D3C-4E85-A42F-18CFE9D94C91",
            "Name": "updatedTransformation",
            "Schema": "{\"updatedKey\": \"updatedValue\"}"
        }
    }
}
```

---

## Delete Transformation

**POST** `{{edgeUrl}}/digital-twins`

# Delete Transformation

Deletes a single transformation by ID. Dynamic attributes that referenced the deleted transformation will fall back to no validation.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation DeleteTransformation($input: DeleteTransformationRequest!) {
  DeleteTransformation(input: $input)
}
```

### Variables

| Field   | Type | Required | Description                                  |
|---------|------|----------|----------------------------------------------|
| `ID`    | `ID` | Yes      | UUID of the transformation.                  |

```json
{
  "input": {
    "ID": "{{digital_twins_transformation_id}}"
  }
}
```

## Response

`200 OK` -- `application/json`. No data returned on success.

```json
{ "data": { "DeleteTransformation": null } }
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation DeleteTransformation ($input: DeleteTransformationRequest!){
    DeleteTransformation(input: $input)
}

```

**Variables**

```json
{
    "input": {
        "ID": "{{digital_twins_transformation_id}}"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "DeleteTransformation": null
    }
}
```

---

## List Parameters

**POST** `{{edgeUrl}}/digital-twins`

# List Parameters

Returns the parameters defined on the given owner. `OwnerID` is the UUID of either a model or an instance. Parameters are free-form `Name`/`Value` strings used by integrations and the LE UI for ad-hoc configuration that isn't surfaced as static/dynamic attributes.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
query ListParameters($input: ListParametersRequest) {
  ListParameters(input: $input) {
    ID
    Name
    Value
  }
}
```

### Variables

| Field      | Type | Required | Description                                            |
|------------|------|----------|--------------------------------------------------------|
| `OwnerID`  | `ID` | Yes      | UUID of the model or instance that owns the parameters.|

```json
{
  "input": {
    "OwnerID": "{{digital_twins_model_id}}"
  }
}
```

## Response

`200 OK` -- `application/json`

| Field                            | Type             | Description                                       |
|----------------------------------|------------------|---------------------------------------------------|
| `data.ListParameters`            | `[Parameter!]!`  | All parameters on the owner.                      |
| `data.ListParameters[].ID`       | `ID`             | Parameter UUID.                                   |
| `data.ListParameters[].Name`     | `String`         | Parameter name.                                   |
| `data.ListParameters[].Value`    | `String`         | Parameter value.                                  |

```json
{
  "data": {
    "ListParameters": [
      { "ID": "51D2E713-6150-4800-8779-9A1825BE19D7", "Name": "Updated key", "Value": "Updated value" },
      { "ID": "8FA45411-ECA0-44C0-BD2F-60341D55C581", "Name": "key1",        "Value": "value1" }
    ]
  }
}
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
query ListParameters($input: ListParametersRequest) {
  ListParameters(input: $input) {
    ID
    Name
    Value
  }
}
```

**Variables**

```json
{
    "input": {
        "OwnerID": "{{digital_twins_model_id}}"
    }
}
```

### Response

**Status**: 200 OK

```Text
{
    "data": {
        "ListParameters": [
            {
                "ID": "51D2E713-6150-4800-8779-9A1825BE19D7",
                "Name": "Updated key",
                "Value": "Updated value"
            },
            {
                "ID": "8FA45411-ECA0-44C0-BD2F-60341D55C581",
                "Name": "key1",
                "Value": "value1"
            }
        ]
    }
}
```

---

## Set Parameters

**POST** `{{edgeUrl}}/digital-twins`

# Set Parameters (transactional)

Applies a set of parameter changes to a model atomically using the **transactional model update** mutation. The same mutation is used to create, update, and delete parameters in one call; `Delete Parameters` uses a payload that includes only the delete portion.

This is the same endpoint used by the LE UI to commit edits to a model's schema in one round-trip.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation TransactionalModelUpdate($input: TransactionalModelUpdateRequest!) {
  TransactionalModelUpdate(input: $input)
}
```

### Variables

The input is an envelope of per-operation arrays. Send only the arrays you need; the others can be empty `Items: []`.

| Field                       | Type           | Description                                                                       |
|-----------------------------|----------------|-----------------------------------------------------------------------------------|
| `modelID`                   | `ID`           | UUID of the target model.                                                         |
| `createStaticAttributes`    | `{ Items }`    | List of `CreateStaticAttribute` payloads to apply.                                |
| `updateStaticAttributes`    | `{ Items }`    | List of `UpdateStaticAttribute` payloads.                                         |
| `delStaticAttributes`       | `{ Items }`    | List of `{ID}` objects for static attributes to delete.                           |
| `createDynamicAttributes`   | `{ Items }`    | List of `CreateDynamicAttribute` payloads.                                        |
| `updateDynamicAttributes`   | `{ Items }`    | List of `UpdateDynamicAttribute` payloads.                                        |
| `delDynamicAttributes`      | `{ Items }`    | List of `{ID}` objects.                                                           |
| `createParameters`          | `{ Items }`    | List of `{Name, Value, OwnerID}` parameter payloads to create.                    |
| `updateParameters`          | `{ Items }`    | List of `{ID, Name, Value}` parameter payloads to update.                         |
| `delParameters`             | `{ Items }`    | List of `{ID}` objects for parameters to delete.                                  |

For the parameter-only use case, leave every attribute-related array empty and populate just `createParameters` / `updateParameters` / `delParameters`.

```json
{
  "input": {
    "modelID": "{{digital_twins_model_id}}",
    "createStaticAttributes":  { "Items": [] },
    "updateStaticAttributes":  { "Items": [] },
    "delStaticAttributes":     { "Items": [] },
    "createDynamicAttributes": { "Items": [] },
    "updateDynamicAttributes": { "Items": [] },
    "delDynamicAttributes":    { "Items": [] }
  }
}
```

## Response

`200 OK` -- `application/json`. The mutation returns no data; failures surface in the GraphQL `errors` array.

```json
{ "data": { "TransactionalModelUpdate": null } }
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation TransactionalModelUpdate($input: TransactionalModelUpdateRequest!) {
    TransactionalModelUpdate(input: $input)
}
```

**Variables**

```json
{
    "input": {
        "modelID": "{{digital_twins_model_id}}",
        "createStaticAttributes": {
            "Items": []
        },
        "updateStaticAttributes": {
            "Items": []
        },
        "delStaticAttributes": {
            "Items": []
        },
        "createDynamicAttributes": {
            "Items": []
        },
        "updateDynamicAttributes": {
            "Items": []
        },
        "delDynamicAttributes": {
            "Items": []
        },
        "createTransformations": [],
        "updateTransformations": [],
        "deleteTransformations": [],
        "setParameters": {
            "Items": [
                {
                    "ID": "8fa45411-eca0-44c0-bd2f-60341d55c581",
                    "Name": "key1",
                    "Value": "value1"
                },
                {
                    "ID": "51d2e713-6150-4800-8779-9a1825be19d7",
                    "Name": "Updated key",
                    "Value": "Updated value"
                }
            ]
        },
        "deleteParameters": {
            "Items": []
        }
    }
}
```

### Response

**Status**: 200 OK

```Text
{
    "data": {
        "TransactionalModelUpdate": null
    }
}
```

---

## Delete Parameters

**POST** `{{edgeUrl}}/digital-twins`

# Delete Parameters (transactional)

Variant of `Set Parameters` for the **delete-only** case. The mutation is identical (`TransactionalModelUpdate`); only the populated arrays differ.

To delete one or more parameters, populate `delParameters.Items` with `{ID}` objects.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation TransactionalModelUpdate($input: TransactionalModelUpdateRequest!) {
  TransactionalModelUpdate(input: $input)
}
```

### Variables (delete-only example)

```json
{
  "input": {
    "modelID": "{{digital_twins_model_id}}",
    "createStaticAttributes":  { "Items": [] },
    "updateStaticAttributes":  { "Items": [] },
    "delStaticAttributes":     { "Items": [] },
    "createDynamicAttributes": { "Items": [] },
    "updateDynamicAttributes": { "Items": []  }
  }
}
```

The above example shows the envelope without any deletions populated -- in practice you would fill `delParameters.Items: [{ID: "..."}]` with the parameter UUIDs to remove. See `Set Parameters` for the full set of arrays the envelope supports.

## Response

`200 OK` -- `application/json`. No data returned on success.

```json
{ "data": { "TransactionalModelUpdate": null } }
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
    mutation TransactionalModelUpdate($input: TransactionalModelUpdateRequest!) {
        TransactionalModelUpdate(input: $input)
    }
```

**Variables**

```json
{
  "input": {
    "modelID": "{{digital_twins_model_id}}",
    "createStaticAttributes": {
      "Items": []
    },
    "updateStaticAttributes": {
      "Items": []
    },
    "delStaticAttributes": {
      "Items": []
    },
    "createDynamicAttributes": {
      "Items": []
    },
    "updateDynamicAttributes": {
      "Items": []
    },
    "delDynamicAttributes": {
      "Items": []
    },
    "createTransformations": [],
    "updateTransformations": [],
    "deleteTransformations": [],
    "setParameters": {
      "Items": [
        {
          "ID": "56daf3aa-bdec-4e7e-8d6a-a5229eaa2988",
          "Name": "hi",
          "Value": "hello"
        }
      ]
    },
    "deleteParameters": {
      "Items": [
        {
          "ID": "51D2E713-6150-4800-8779-9A1825BE19D7"
        },
        {
          "ID": "56DAF3AA-BDEC-4E7E-8D6A-A5229EAA2988"
        }
      ]
    }
  }
}
```

### Response

**Status**: 200 OK

```Text
{
    "data": {
        "TransactionalModelUpdate": null
    }
}
```

---

## Get Hierarchy

**POST** `{{edgeUrl}}/digital-twins`

# Get Hierarchy

Returns the current hierarchy of a model as a nested tree. The hierarchy defines how attributes are organized into folders inside the model -- the same structure the LE UI shows in the model editor's tree view.

Each node references either an attribute (via `AttributeID`) or is a folder (`IsFolder: true`). Nodes may have child nodes via `Childs`.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
query GetHierarchy($input: GetHierarchyRequest!) {
  GetHierarchy(input: $input)
}
```

### Variables

| Field      | Type | Required | Description                                  |
|------------|------|----------|----------------------------------------------|
| `ModelID`  | `ID` | Yes      | UUID of the model.                           |

```json
{
  "input": {
    "ModelID": "{{digital_twins_model_id}}"
  }
}
```

## Response

`200 OK` -- `application/json`. `data.GetHierarchy` is a single root node:

| Field             | Type     | Description                                                                |
|-------------------|----------|----------------------------------------------------------------------------|
| `Name`            | `String` | Node display name. `"root"` for the synthetic root.                        |
| `Node`            | `Node?`  | Backing node record (`null` for the synthetic root).                       |
| `Node.ID`         | `ID`     | Node UUID.                                                                 |
| `Node.ModelID`    | `ID`     | Parent model.                                                              |
| `Node.Position`   | `Int`    | Display order among siblings (0-indexed).                                  |
| `Node.ParentID`   | `ID?`    | UUID of the parent node, or `null` for top-level nodes.                    |
| `Node.IsFolder`   | `Boolean`| `true` for organizational folders; `false` for attribute references.       |
| `Node.AttributeID`| `ID?`    | UUID of the referenced static or dynamic attribute. Empty for folders.     |
| `Attr`            | object?  | Resolved attribute record (denormalized for UI convenience).               |
| `Childs`          | `[Node]` | Child nodes (recursive).                                                   |

```json
{
  "data": {
    "GetHierarchy": {
      "Name": "root",
      "Node": null,
      "Attr": null,
      "Childs": [
        {
          "Name": "Manufacturer",
          "Node": {
            "ID": "d476d3df-8794-4d57-a991-623e8811fb6b",
            "ModelID": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
            "Position": 0,
            "ParentID": null,
            "Name": "Manufacturer",
            "IsFolder": false,
            "AttributeID": "189edb70-1029-4ebf-ab40-27e5423c12ce"
          }
        }
      ]
    }
  }
}
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
query GetHierarchy ($input : GetHierarchyRequest!){
    GetHierarchy(input: $input)
}

```

**Variables**

```json
{
    "input": {
        "ModelID": "{{digital_twins_model_id}}"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "GetHierarchy": {
            "Name": "root",
            "Node": null,
            "Attr": null,
            "Childs": [
                {
                    "Name": "Manufacturer",
                    "Node": {
                        "ID": "d476d3df-8794-4d57-a991-623e8811fb6b",
                        "ModelID": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                        "Position": 0,
                        "ParentID": null,
                        "Name": "Manufacturer",
                        "IsFolder": false,
                        "AttributeID": "189edb70-1029-4ebf-ab40-27e5423c12ce",
                        "AttributeType": "static",
                        "CreatedAt": "2023-08-17T17:31:55.670316457Z",
                        "IndexUniqueAttrID": "root189edb70-1029-4ebf-ab40-27e5423c12ce",
                        "IndexType": "hierarchy_nodes",
                        "IndexModelID": "hierarchy_nodes3a923971-5dfc-4633-b5af-a6bf8c0b60e9"
                    },
                    "Attr": {
                        "ID": "189edb70-1029-4ebf-ab40-27e5423c12ce",
                        "ParentID": "00000000-0000-0000-0000-000000000000",
                        "ModelID": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                        "InstanceID": "00000000-0000-0000-0000-000000000000",
                        "Key": "Manufacturer",
                        "Value": "API Manufacturer",
                        "CreatedAt": "2023-08-16T00:03:50.174589529Z",
                        "UpdatedAt": "2023-08-16T00:23:38.740405183Z",
                        "IndexType": "static_attributes",
                        "IndexModelID": "static_attributes3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                        "IndexInstanceID": "static_attributes00000000-0000-0000-0000-000000000000",
                        "IndexUniqueModelIDName": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9Manufacturer"
                    },
                    "Childs": []
                },
                {
                    "Name": "New_Node",
                    "Node": {
                        "ID": "2562cb6d-36c1-47b2-bfac-1495377db3a5",
                        "ModelID": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                        "Position": 1,
                        "ParentID": null,
                        "Name": "New_Node",
                        "IsFolder": true,
                        "AttributeID": null,
                        "AttributeType": null,
                        "CreatedAt": "2023-08-17T17:31:55.670579242Z",
                        "IndexUniqueAttrID": "root2562cb6d-36c1-47b2-bfac-1495377db3a5",
                        "IndexType": "hierarchy_nodes",
                        "IndexModelID": "hierarchy_nodes3a923971-5dfc-4633-b5af-a6bf8c0b60e9"
                    },
                    "Attr": null,
                    "Childs": [
                        {
                            "Name": "Model",
                            "Node": {
                                "ID": "e028e1d1-9212-4e5b-8290-46104491ce93",
                                "ModelID": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                                "Position": 0,
                                "ParentID": "2562cb6d-36c1-47b2-bfac-1495377db3a5",
                                "Name": "Model",
                                "IsFolder": false,
                                "AttributeID": "3f44c811-d8cc-4735-b337-a0f30826641e",
                                "AttributeType": "static",
                                "CreatedAt": "2023-08-17T17:31:55.670918962Z",
                                "IndexUniqueAttrID": "2562cb6d-36c1-47b2-bfac-1495377db3a53f44c811-d8cc-4735-b337-a0f30826641e",
                                "IndexType": "hierarchy_nodes",
                                "IndexModelID": "hierarchy_nodes3a923971-5dfc-4633-b5af-a6bf8c0b60e9"
                            },
                            "Attr": {
                                "ID": "3f44c811-d8cc-4735-b337-a0f30826641e",
                                "ParentID": "00000000-0000-0000-0000-000000000000",
                                "ModelID": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                                "InstanceID": "00000000-0000-0000-0000-000000000000",
                                "Key": "Model",
                                "Value": "New API Model",
                                "CreatedAt": "2023-08-16T17:29:11.461961315Z",
                                "UpdatedAt": "2023-08-16T17:29:11.461962629Z",
                                "IndexType": "static_attributes",
                                "IndexModelID": "static_attributes3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                                "IndexInstanceID": "static_attributes00000000-0000-0000-0000-000000000000",
                                "IndexUniqueModelIDName": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9Model"
                            },
                            "Childs": []
                        }
                    ]
                }
            ]
        }
    }
}
```

---

## Save All Hierarchy

**POST** `{{edgeUrl}}/digital-twins`

# Save All Hierarchy

Replaces the entire hierarchy of a model in a single call. The mutation accepts the same nested tree shape that `Get Hierarchy` returns (with arrays of `{Node, Childs}` entries). Existing hierarchy nodes are reconciled; missing ones are deleted; new ones are created.

Use this when programmatically building or editing a model's tree -- it is much cheaper than per-node mutations.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation SaveAllHierarchy($modelId: UUID!, $input: [SaveAllHierarchyRequest]!) {
  SaveAllHierarchy(modelId: $modelId, input: $input)
}
```

### Variables

| Field      | Type                          | Required | Description                                                                                                      |
|------------|-------------------------------|----------|------------------------------------------------------------------------------------------------------------------|
| `modelId`  | `UUID!`                       | Yes      | UUID of the model whose hierarchy to replace.                                                                    |
| `input`    | `[SaveAllHierarchyRequest]!`  | Yes      | Top-level nodes. Each node has `Node` (a `{AttributeID, AttributeType, IsFolder, Name, Position}` payload) and `Childs` (recursive). |

```json
{
  "modelId": "{{digital_twins_model_id}}",
  "input": [
    {
      "Node": {
        "AttributeID": "189edb70-1029-4ebf-ab40-27e5423c12ce",
        "AttributeType": "static",
        "IsFolder": false,
        "Name": "Manufacturer",
        "Position": 0
      },
      "Childs": []
    }
  ]
}
```

`AttributeType` is `"static"` or `"dynamic"` and must match the kind of the attribute referenced by `AttributeID`.

## Response

`200 OK` -- `application/json`. Returns the persisted nodes with their freshly-assigned/refreshed UUIDs.

```json
{
  "data": {
    "SaveAllHierarchy": [
      {
        "ID": "71ae472d-b2d5-43fd-8824-80aee150791b",
        "ModelID": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
        "Position": 0,
        "ParentID": "8cd29a23-816e-4f53-bd88-aed2a0564724",
        "Name": "Model",
        "IsFolder": false,
        "AttributeID": "3f44c811-d8cc-4735-b337-a0f30826641e",
        "AttributeType": "static",
        "CreatedAt": "2023-08-18T21:57:49.16464345Z"
      }
    ]
  }
}
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation SaveAllHierarchy($modelId: UUID!, $input: [SaveAllHierarchyRequest]!) {
  SaveAllHierarchy(modelId: $modelId, input: $input)
}
```

**Variables**

```json
{
  "modelId": "{{digital_twins_model_id}}",
  "input": [
    {
      "Node": {
        "AttributeID": "189edb70-1029-4ebf-ab40-27e5423c12ce",
        "AttributeType": "static",
        "IsFolder": false,
        "Name": "Manufacturer",
        "Position": 0
      },
      "Childs": []
    },
    {
      "Node": {
        "AttributeID": null,
        "AttributeType": null,
        "IsFolder": true,
        "Name": "New_Node",
        "Position": 1
      },
      "Childs": [
        {
          "Node": {
            "AttributeID": "3f44c811-d8cc-4735-b337-a0f30826641e",
            "AttributeType": "static",
            "IsFolder": false,
            "Name": "Model",
            "Position": 0
          },
          "Childs": []
        },
        {
          "Node": {
            "AttributeID": "31A6EDDC-A455-4126-981C-0A6313915A89",
            "AttributeType": "dynamic",
            "IsFolder": false,
            "Name": "Speed",
            "Position": 1
          },
          "Childs": []
        },
        {
          "Node": {
            "AttributeID": null,
            "AttributeType": null,
            "IsFolder": true,
            "Name": "New_Node",
            "Position": 2
          },
          "Childs": [
            {
              "Node": {
                "AttributeID": "3F44C811-D8CC-4735-B337-A0F30826641E",
                "AttributeType": "static",
                "IsFolder": false,
                "Name": "Model",
                "Position": 0
              },
              "Childs": []
            }
          ]
        }
      ]
    }
  ]
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "SaveAllHierarchy": [
            {
                "ID": "71ae472d-b2d5-43fd-8824-80aee150791b",
                "ModelID": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                "Position": 0,
                "ParentID": "8cd29a23-816e-4f53-bd88-aed2a0564724",
                "Name": "Model",
                "IsFolder": false,
                "AttributeID": "3f44c811-d8cc-4735-b337-a0f30826641e",
                "AttributeType": "static",
                "CreatedAt": "2023-08-18T21:57:49.16464345Z",
                "IndexUniqueAttrID": "8cd29a23-816e-4f53-bd88-aed2a05647243f44c811-d8cc-4735-b337-a0f30826641e",
                "IndexType": "hierarchy_nodes",
                "IndexModelID": "hierarchy_nodes3a923971-5dfc-4633-b5af-a6bf8c0b60e9"
            },
            {
                "ID": "8424515e-a7f8-4cf4-b577-8ef999388538",
                "ModelID": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                "Position": 0,
                "ParentID": null,
                "Name": "Manufacturer",
                "IsFolder": false,
                "AttributeID": "189edb70-1029-4ebf-ab40-27e5423c12ce",
                "AttributeType": "static",
                "CreatedAt": "2023-08-18T21:57:49.163896372Z",
                "IndexUniqueAttrID": "root189edb70-1029-4ebf-ab40-27e5423c12ce",
                "IndexType": "hierarchy_nodes",
                "IndexModelID": "hierarchy_nodes3a923971-5dfc-4633-b5af-a6bf8c0b60e9"
            },
            {
                "ID": "d0d06af4-be65-4d2b-943c-4b2487f7e365",
                "ModelID": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                "Position": 0,
                "ParentID": "92c483ac-5949-4a55-84c5-4e55c281ff87",
                "Name": "Model",
                "IsFolder": false,
                "AttributeID": "3f44c811-d8cc-4735-b337-a0f30826641e",
                "AttributeType": "static",
                "CreatedAt": "2023-08-18T21:57:49.165856053Z",
                "IndexUniqueAttrID": "92c483ac-5949-4a55-84c5-4e55c281ff873f44c811-d8cc-4735-b337-a0f30826641e",
                "IndexType": "hierarchy_nodes",
                "IndexModelID": "hierarchy_nodes3a923971-5dfc-4633-b5af-a6bf8c0b60e9"
            },
            {
                "ID": "14ce6813-11b7-41d0-b785-ce1fdcdaf6b0",
                "ModelID": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                "Position": 1,
                "ParentID": "8cd29a23-816e-4f53-bd88-aed2a0564724",
                "Name": "Speed",
                "IsFolder": false,
                "AttributeID": "31a6eddc-a455-4126-981c-0a6313915a89",
                "AttributeType": "dynamic",
                "CreatedAt": "2023-08-18T21:57:49.165069353Z",
                "IndexUniqueAttrID": "8cd29a23-816e-4f53-bd88-aed2a056472431a6eddc-a455-4126-981c-0a6313915a89",
                "IndexType": "hierarchy_nodes",
                "IndexModelID": "hierarchy_nodes3a923971-5dfc-4633-b5af-a6bf8c0b60e9"
            },
            {
                "ID": "8cd29a23-816e-4f53-bd88-aed2a0564724",
                "ModelID": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                "Position": 1,
                "ParentID": null,
                "Name": "New_Node",
                "IsFolder": true,
                "AttributeID": null,
                "AttributeType": null,
                "CreatedAt": "2023-08-18T21:57:49.164185815Z",
                "IndexUniqueAttrID": "root8cd29a23-816e-4f53-bd88-aed2a0564724",
                "IndexType": "hierarchy_nodes",
                "IndexModelID": "hierarchy_nodes3a923971-5dfc-4633-b5af-a6bf8c0b60e9"
            },
            {
                "ID": "92c483ac-5949-4a55-84c5-4e55c281ff87",
                "ModelID": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                "Position": 2,
                "ParentID": "8cd29a23-816e-4f53-bd88-aed2a0564724",
                "Name": "New_Node",
                "IsFolder": true,
                "AttributeID": null,
                "AttributeType": null,
                "CreatedAt": "2023-08-18T21:57:49.165520481Z",
                "IndexUniqueAttrID": "8cd29a23-816e-4f53-bd88-aed2a056472492c483ac-5949-4a55-84c5-4e55c281ff87",
                "IndexType": "hierarchy_nodes",
                "IndexModelID": "hierarchy_nodes3a923971-5dfc-4633-b5af-a6bf8c0b60e9"
            }
        ]
    }
}
```

---

## List all Dynamic Attributes

**POST** `{{edgeUrl}}/digital-twins`

# List all Dynamic Attributes

Lists every dynamic attribute defined on a model. A **dynamic attribute** is a live signal -- it carries a real-time value and is bound to a tag topic from DeviceHub (or another publisher).

For instance-level overrides, see `Instances > Dynamic Attribute > List all Dynamic Attributes`.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
query ListDynamicAttributes($input: ListDynamicAttributeRequest!) {
  ListDynamicAttributes(input: $input) {
    ID
    ModelID
    InstanceID
    Topic
    Name
    Unit
    DataType
    SchemaID
    CreatedAt
    UpdatedAt
  }
}
```

### Variables

| Field       | Type | Required | Description                                  |
|-------------|------|----------|----------------------------------------------|
| `ModelID`   | `ID` | Yes      | UUID of the model.                           |

```json
{
  "input": {
    "ModelID": "{{digital_twins_model_id}}"
  }
}
```

## Response

`200 OK` -- `application/json`

| Field                                     | Type             | Description                                                                       |
|-------------------------------------------|------------------|-----------------------------------------------------------------------------------|
| `data.ListDynamicAttributes`              | `[DynamicAttr!]!`| All dynamic attributes for the scope.                                             |
| `data.ListDynamicAttributes[].ID`         | `ID`             | Attribute UUID.                                                                   |
| `data.ListDynamicAttributes[].ModelID`    | `ID`             | Parent model.                                                                     |
| `data.ListDynamicAttributes[].InstanceID` | `ID`             | Parent instance (`00000000-...` for model-level definitions).                     |
| `data.ListDynamicAttributes[].Topic`      | `String`         | Source topic for the live value. Empty for model-level definitions; set per-instance to bind to a DeviceHub tag topic. |
| `data.ListDynamicAttributes[].Name`       | `String`         | Attribute name.                                                                   |
| `data.ListDynamicAttributes[].Unit`       | `String`         | Engineering unit string (`kmph`, `degC`, ...).                                    |
| `data.ListDynamicAttributes[].DataType`   | `String`         | Data type (`string`, `int`, `float`, `bool`, `Dictionary`, ...).                  |
| `data.ListDynamicAttributes[].SchemaID`   | `ID`             | UUID of the transformation/JSON schema applied to payloads, or empty.             |
| `data.ListDynamicAttributes[].CreatedAt`/`.UpdatedAt` | `Time` | ISO 8601 timestamps.                                                       |

```json
{
  "data": {
    "ListDynamicAttributes": [
      {
        "ID": "77990102-3613-4D2B-ABFC-EE761383E631",
        "ModelID": "3A923971-5DFC-4633-B5AF-A6BF8C0B60E9",
        "InstanceID": "00000000-0000-0000-0000-000000000000",
        "Topic": "",
        "Name": "Dynamic attribute 1",
        "Unit": "test1",
        "DataType": "Dictionary",
        "SchemaID": "30D86400-D103-4C5C-95E4-AB6F02FB00EE"
      }
    ]
  }
}
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
query ListDynamicAttributes ($input: ListDynamicAttributeRequest!) {
    ListDynamicAttributes(input:$input) {
        ID
        ModelID
        InstanceID
        Topic
        Name
        Unit
        DataType
        SchemaID
        CreatedAt
        UpdatedAt
    }
}

```

**Variables**

```json
{
    "input": {
        "ModelID": "{{digital_twins_model_id}}"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "ListDynamicAttributes": [
            {
                "ID": "77990102-3613-4D2B-ABFC-EE761383E631",
                "ModelID": "3A923971-5DFC-4633-B5AF-A6BF8C0B60E9",
                "InstanceID": "00000000-0000-0000-0000-000000000000",
                "Topic": "",
                "Name": "Dynamic attribute 1",
                "Unit": "test1",
                "DataType": "Dictionary",
                "SchemaID": "30D86400-D103-4C5C-95E4-AB6F02FB00EE",
                "CreatedAt": "2023-08-16T19:13:13.808757635Z",
                "UpdatedAt": "2023-08-16T20:17:31.074886398Z"
            }
        ]
    }
}
```

---

## Create Dynamic Attribute

**POST** `{{edgeUrl}}/digital-twins`

# Create Dynamic Attribute

Adds a new dynamic attribute to a model. The new attribute is part of the model schema and will appear on every instance after creation (with empty `Topic` until each instance is bound).

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation CreateDynamicAttribute($input: CreateDynamicAttributeRequest!) {
  CreateDynamicAttribute(input: $input) {
    ID
    ModelID
    InstanceID
    Topic
    Name
    Unit
    DataType
    SchemaID
    CreatedAt
    UpdatedAt
  }
}
```

### Variables

| Field      | Type     | Required | Description                                                            |
|------------|----------|----------|------------------------------------------------------------------------|
| `ModelID`  | `ID`     | Yes      | UUID of the parent model.                                              |
| `Name`     | `String` | Yes      | Attribute name. Must be unique within the model.                       |
| `Unit`     | `String` | No       | Engineering unit string.                                               |
| `DataType` | `String` | Yes      | Data type of the live value (`string`, `int`, `float`, `bool`, `Dictionary`, ...). |
| `SchemaID` | `ID`     | No       | Transformation (JSON Schema) to apply to the payload. Use `00000000-...` for none. |
| `Topic`    | `String` | No       | Source topic. Usually left empty at model scope; set per-instance via `Save Instance/Add Topic to Dynamic Attribute`. |

```json
{
  "input": {
    "ModelID": "{{digital_twins_model_id}}",
    "Name": "Speed",
    "Unit": "kmph",
    "DataType": "string",
    "SchemaID": "30D86400-D103-4C5C-95E4-AB6F02FB00EE",
    "Topic": ""
  }
}
```

## Response

`200 OK` -- `application/json`. Returns the new attribute with the same shape as `List all Dynamic Attributes`.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation CreateDynamicAttribute ($input: CreateDynamicAttributeRequest!) {
    CreateDynamicAttribute(input: $input) {
        ID
        ModelID
        InstanceID
        Topic
        Name
        Unit
        DataType
        SchemaID
        CreatedAt
        UpdatedAt
    }
}

```

**Variables**

```json
{
    "input": {
        "ModelID": "{{digital_twins_model_id}}",
        "Name": "Speed",
        "Unit": "kmph",
        "DataType": "string",
        "SchemaID": "30D86400-D103-4C5C-95E4-AB6F02FB00EE",
        "Topic": ""
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "CreateDynamicAttribute": {
            "ID": "5B825E58-D3F6-4E0D-B1B2-C172F893D0DF",
            "ModelID": "3A923971-5DFC-4633-B5AF-A6BF8C0B60E9",
            "InstanceID": "00000000-0000-0000-0000-000000000000",
            "Topic": "",
            "Name": "Speed",
            "Unit": "kmph",
            "DataType": "string",
            "SchemaID": "30D86400-D103-4C5C-95E4-AB6F02FB00EE",
            "CreatedAt": "2023-08-16T20:24:54.832165611Z",
            "UpdatedAt": "2023-08-16T20:24:54.832165815Z"
        }
    }
}
```

---

## Update Dynamic Attribute

**POST** `{{edgeUrl}}/digital-twins`

# Update Dynamic Attribute

Replaces the editable fields of an existing dynamic attribute. The same mutation is used for model-level and instance-level updates; pass the appropriate `ModelID`/`InstanceID` to scope.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation UpdateDynamicAttribute($input: UpdateDynamicAttributeRequest!) {
  UpdateDynamicAttribute(input: $input) {
    ID
    ModelID
    InstanceID
    Topic
    Name
    Unit
    DataType
    SchemaID
    CreatedAt
    UpdatedAt
  }
}
```

### Variables

| Field      | Type     | Required | Description                                                       |
|------------|----------|----------|-------------------------------------------------------------------|
| `ID`       | `ID`     | Yes      | UUID of the dynamic attribute to update.                          |
| `ModelID`  | `ID`     | Yes      | UUID of the parent model.                                         |
| `Name`     | `String` | Yes      | New attribute name.                                               |
| `Unit`     | `String` | No       | New engineering unit.                                             |
| `DataType` | `String` | Yes      | New data type.                                                    |
| `SchemaID` | `ID`     | No       | New transformation UUID.                                          |
| `Topic`    | `String` | No       | New source topic (empty for model scope).                         |

```json
{
  "input": {
    "ID": "{{digital_twins_dynamic_attribute_id}}",
    "ModelID": "{{digital_twins_model_id}}",
    "Name": "changedName",
    "Unit": "changedUnit",
    "DataType": "string",
    "SchemaID": "30D86400-D103-4C5C-95E4-AB6F02FB00EE",
    "Topic": ""
  }
}
```

## Response

`200 OK` -- `application/json`. Returns the updated attribute.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation UpdateDynamicAttribute ($input: UpdateDynamicAttributeRequest!) {
    UpdateDynamicAttribute(input: $input) {
        ID
        ModelID
        InstanceID
        Topic
        Name
        Unit
        DataType
        SchemaID
        CreatedAt
        UpdatedAt
    }
}
```

**Variables**

```json
{
    "input": {
        "ID": "{{digital_twins_dynamic_attribute_id}}",
        "ModelID": "{{digital_twins_model_id}}",
        "Name": "changedName",
        "Unit": "changedUnit",
        "DataType": "string",
        "SchemaID": "30D86400-D103-4C5C-95E4-AB6F02FB00EE",
        "Topic": ""
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "UpdateDynamicAttribute": {
            "ID": "77990102-3613-4D2B-ABFC-EE761383E631",
            "ModelID": "3A923971-5DFC-4633-B5AF-A6BF8C0B60E9",
            "InstanceID": "00000000-0000-0000-0000-000000000000",
            "Topic": "",
            "Name": "changedName",
            "Unit": "changedUnit",
            "DataType": "string",
            "SchemaID": "30D86400-D103-4C5C-95E4-AB6F02FB00EE",
            "CreatedAt": "2023-08-16T19:13:13.808757635Z",
            "UpdatedAt": "2023-08-16T20:27:34.892193102Z"
        }
    }
}
```

---

## Download Dynamic Attributes

**POST** `{{edgeUrl}}/digital-twins`

# Download Dynamic Attributes

Exports the dynamic attributes of a model as a CSV string. The CSV is compatible with `Upload Dynamic Attribute CSV` for round-trip workflows: download, edit in a spreadsheet, re-upload.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
query DownloadDynamicAttributesCSV($input: DownloadCSVRequest!) {
  DownloadDynamicAttributesCSV(input: $input)
}
```

### Variables

| Field      | Type | Required | Description                                  |
|------------|------|----------|----------------------------------------------|
| `ModelID`  | `ID` | Yes      | UUID of the model to export.                 |

```json
{
  "input": {
    "ModelID": "{{digital_twins_model_id}}"
  }
}
```

## Response

`200 OK` -- `application/json`. `data.DownloadDynamicAttributesCSV` is a **CSV-formatted string** with header row `ID,ModelName,InstanceName,Topic,Name,Unit,DataType,SchemaID`.

```json
{
  "data": {
    "DownloadDynamicAttributesCSV": "ID,ModelName,InstanceName,Topic,Name,Unit,DataType,SchemaID\n68280ec1-1c12-4b0d-a7ea-5a9d5ac32092,apiImportModel,,,Speed,kmph,string,4c67a3e1-d442-4b39-b0f6-eb07cc200066\n"
  }
}
```

Parse the string client-side (it is not a separate file download). To get a true file download, take the string and write it to disk yourself.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
query DownloadDynamicAttributesCSV($input: DownloadCSVRequest!) {
    DownloadDynamicAttributesCSV(input: $input)
}
```

**Variables**

```json
{
    "input": {
        "ModelID": "{{digital_twins_model_id}}"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "DownloadDynamicAttributesCSV": "ID,ModelName,InstanceName,Topic,Name,Unit,DataType,SchemaID\n68280ec1-1c12-4b0d-a7ea-5a9d5ac32092,apiImportModel,,,Speed,kmph,string,4c67a3e1-d442-4b39-b0f6-eb07cc200066\n"
    }
}
```

---

## Upload Dynamic Attribute CSV

**POST** `{{edgeUrl}}/digital-twins`

# Upload Dynamic Attribute CSV

Imports dynamic attributes from a CSV file. Uses the GraphQL multipart spec, identical in shape to `Models > Static Attributes > Upload an Image`.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body (form-data parts)

| Form key      | Type    | Description                                                                                                |
|---------------|---------|------------------------------------------------------------------------------------------------------------|
| `operations`  | `text`  | GraphQL envelope describing the mutation, with `null` placeholder for the file.                            |
| `map`         | `text`  | JSON mapping form file keys to placeholder locations within `operations`.                                  |
| `1`           | `file`  | The CSV file. The form key (`1`) must match the key referenced in `map`.                                   |

### `operations` value

```json
{
  "operationName": "UploadDynamicAttributesCSV",
  "variables": { "input": { "ModelID": "{{digital_twins_model_id}}", "File": null } },
  "query": "mutation UploadDynamicAttributesCSV($input: UploadCSVRequest!) { UploadDynamicAttributesCSV(input: $input) { Errors { Line Column Error } } }"
}
```

### `map` value

```json
{ "1": ["variables.input.File"] }
```

The CSV format must match what `Download Dynamic Attributes` produces (see that endpoint for the header row).

## Response

`200 OK` -- `application/json`

| Field                                                | Type            | Description                                                         |
|------------------------------------------------------|-----------------|---------------------------------------------------------------------|
| `data.UploadDynamicAttributesCSV.Errors`             | `[CSVError!]!`  | One entry per error row, plus a summary row.                        |
| `data.UploadDynamicAttributesCSV.Errors[].Line`      | `Int?`          | 1-indexed CSV line number with the error. `null` for the summary.   |
| `data.UploadDynamicAttributesCSV.Errors[].Column`    | `Int?`          | 1-indexed column number with the error. `null` for the summary.     |
| `data.UploadDynamicAttributesCSV.Errors[].Error`     | `String`        | Error text (or `created: N updated: N skipped: N` summary).         |

```json
{
  "data": {
    "UploadDynamicAttributesCSV": {
      "Errors": [
        {
          "Line": null,
          "Column": null,
          "Error": "created: 0 updated: 1 skipped: 0"
        }
      ]
    }
  }
}
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

### Response

**Status**: 200 OK

```json
{
    "data": {
        "UploadDynamicAttributesCSV": {
            "Errors": [
                {
                    "Line": null,
                    "Column": null,
                    "Error": "created: 0 updated: 1 skipped: 0",
                    "__typename": "CSVError"
                }
            ],
            "__typename": "UploadCSVResponse"
        }
    }
}
```

---

## Remove Dynamic Attribute

**POST** `{{edgeUrl}}/digital-twins`

# Remove Dynamic Attribute

Deletes one or more dynamic attributes by ID. Despite the singular name, the underlying mutation accepts a list -- but the example below shows the single-ID form taken by the Postman item.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation DeleteDynamicAttribute($input: DeleteDynamicAttributeRequest!) {
  DeleteDynamicAttribute(input: $input)
}
```

### Variables

| Field   | Type | Required | Description                                  |
|---------|------|----------|----------------------------------------------|
| `ID`    | `ID` | Yes      | UUID of the dynamic attribute to delete.     |

```json
{
  "input": {
    "ID": "{{digital_twins_dynamic_attribute_id}}"
  }
}
```

## Response

`200 OK` -- `application/json`. No data returned on success.

```json
{ "data": { "DeleteDynamicAttribute": null } }
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation DeleteDynamicAttribute ($input: DeleteDynamicAttributeRequest!) {
    DeleteDynamicAttribute(input: $input)
}

```

**Variables**

```json
{
    "input": {
        "ID": "{{digital_twins_dynamic_attribute_id}}"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "DeleteDynamicAttribute": null
    }
}
```

---

## Digital Twins Version

**POST** `{{edgeUrl}}/digital-twins`

# Digital Twins Version

Returns the running version and git revision of the Digital Twins service. Use as a fast liveness probe or to record the build version for support tickets.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
query Version {
  Version {
    Git
    Version
  }
}
```

## Response

`200 OK` -- `application/json`

| Field                  | Type     | Description                                                       |
|------------------------|----------|-------------------------------------------------------------------|
| `data.Version.Git`     | `String` | Short git commit hash of the deployed build.                      |
| `data.Version.Version` | `String` | Semantic version of the Digital Twins service.                    |

```json
{
  "data": {
    "Version": {
      "Git": "20e4e53e40",
      "Version": "1.2.4"
    }
  }
}
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
query ListModels {
    Version {
        Git
        Version
    }
}

```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "Version": {
            "Git": "20e4e53e40",
            "Version": "1.2.4"
        }
    }
}
```

---

## List Models

**POST** `{{edgeUrl}}/digital-twins`

# List Models

Returns every model defined on the device, projected to a minimal `{ID, Name}` shape. Use as the source for a model picker, or to enumerate models when iterating over instances. For full schema, extend the GraphQL selection (e.g. add `Description`, `Assets`, `IsUNS`, `ConnectorName`).

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
query ListModels {
  ListModels {
    ID
    Name
  }
}
```

## Response

`200 OK` -- `application/json`

| Field                          | Type        | Description                                  |
|--------------------------------|-------------|----------------------------------------------|
| `data.ListModels`              | `[Model!]!` | All models on the device.                    |
| `data.ListModels[].ID`         | `ID`        | Model UUID.                                  |
| `data.ListModels[].Name`       | `String`    | Display name. Unique across models.          |

```json
{
  "data": {
    "ListModels": [
      { "ID": "D9117AE5-3F3E-4436-9A2A-6DD725FBD8FB", "Name": "Digital-Twin-1" }
    ]
  }
}
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
query ListModels {
    ListModels {
        ID
        Name
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "ListModels": [
            {
                "ID": "D9117AE5-3F3E-4436-9A2A-6DD725FBD8FB",
                "Name": "Digital-Twin-1"
            }
        ]
    }
}
```

---

## Create Model

**POST** `{{edgeUrl}}/digital-twins`

# Create Model

Creates a new digital twin model with the minimum properties needed for the LE UI to show it on the Models page. After creation, populate the schema via the `Static Attributes`, `Dynamic Attribute`, `Transformation`, and `Hierarchy` subfolders.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation CreateModel($input: CreateModelRequest!) {
  CreateModel(input: $input) {
    ID
    Name
    Assets
    Description
    IsUNS
    ConnectorName
  }
}
```

### Variables (`input: CreateModelRequest!`)

| Field           | Type     | Required | Description                                                                                          |
|-----------------|----------|----------|------------------------------------------------------------------------------------------------------|
| `Name`          | `String` | Yes      | Display name. Must be unique across all models.                                                      |
| `Assets`        | `String` | Yes      | Asset category for organising models. Default `ASSET`. Free-form, used for grouping in the UI.        |
| `Description`   | `String` | No       | Free-form description.                                                                               |
| `IsUNS`         | `Boolean`| No       | Default `false`. `true` if this model participates in the Unified Namespace via a UNS connector.      |
| `ConnectorName` | `String` | No       | UNS connector to publish under when `IsUNS: true` (e.g. `LitmusEdge`).                                |

```json
{
  "input": {
    "Name": "{{digital_twins_model}}",
    "Assets": "ASSET",
    "Description": "",
    "IsUNS": true,
    "ConnectorName": "{{uns_connector_name}}"
  }
}
```

## Response

`200 OK` -- `application/json`

| Field                          | Type    | Description                                                       |
|--------------------------------|---------|-------------------------------------------------------------------|
| `data.CreateModel.ID`          | `ID`    | UUID of the newly created model. Save as `{{digital_twins_model_id}}`. |
| `data.CreateModel.Name`        | `String`| Model name echoed back.                                           |
| `data.CreateModel.Assets`      | `String`| Asset category echoed back.                                       |
| `data.CreateModel.Description` | `String`| Description echoed back.                                          |

```json
{
  "data": {
    "CreateModel": {
      "ID": "9C01CCFC-7A94-4554-AFAD-BB14942D5190",
      "Name": "apiTest",
      "Assets": "ASSET",
      "Description": "",
      "__typename": "Model"
    }
  }
}
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation CreateModel($input: CreateModelRequest!) {
    CreateModel(input: $input) {
        ID
        Name
        Assets
        Description
        IsUNS
        ConnectorName
    }
}
```

**Variables**

```json
{
  "input": {
    "Name": "{{digital_twins_model}}",
    "Assets": "ASSET",
    "Description": "",
    "IsUNS": true,
    "ConnectorName": "{{uns_connector_name}}"
  }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "CreateModel": {
            "ID": "9C01CCFC-7A94-4554-AFAD-BB14942D5190",
            "Name": "apiTest",
            "Assets": "ASSET",
            "Description": "",
            "__typename": "Model"
        }
    }
}
```

---

## Update Model

**POST** `{{edgeUrl}}/digital-twins`

# Update Model

Updates the mutable top-level fields of a model. To edit the model's schema (attributes, hierarchy, transformations), use the sub-folder endpoints rather than this one.

`UpdateModel` is a full replacement of the input shape -- include every field you want to keep.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation UpdateModel($input: UpdateModelRequest!) {
  UpdateModel(input: $input) {
    ID
    Name
    Assets
    Description
    IsUNS
    ConnectorName
    __typename
  }
}
```

### Variables (`input: UpdateModelRequest!`)

Same fields as `Create Model`, plus:

| Field   | Type | Required | Description                                  |
|---------|------|----------|----------------------------------------------|
| `ID`    | `ID` | Yes      | UUID of the model to update.                 |

```json
{
  "input": {
    "ID": "{{digital_twins_model_id}}",
    "Name": "{{digital_twins_model}}",
    "Assets": "ASSET",
    "Description": "",
    "IsUNS": true,
    "ConnectorName": "{{uns_connector_name}}"
  }
}
```

## Response

`200 OK` -- `application/json`. Echoes the model's new state.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation UpdateModel($input: UpdateModelRequest!) {
    UpdateModel(input: $input) {
        ID
        Name
        Assets
        Description
        IsUNS
        ConnectorName
        __typename
    }
}
```

**Variables**

```json
{
    "input": {
        "Name": "{{digital_twins_model}}",
        "Assets": "ASSET",
        "Description": "",
        "ID": "{{digital_twins_model_id}}",
        "IsUNS": true,
        "ConnectorName": "{{uns_connector_name}}"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "UpdateModel": {
            "ID": "5CF29323-A2C7-48E3-A356-6A046AF99B7F",
            "Name": "apiTest",
            "Assets": "ASSET",
            "Description": "",
            "IsUNS": true,
            "ConnectorName": "LitmusEdge",
            "__typename": "Model"
        }
    }
}
```

---

## Delete Model

**POST** `{{edgeUrl}}/digital-twins`

# Delete Model

Deletes a model and **all of its instances, attributes, transformations, and hierarchy**. Destructive and not idempotent: deleting a missing ID returns a `BAD_USER_INPUT` error.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation DeleteModel($input: DeleteModelRequest!) {
  DeleteModel(input: $input)
}
```

### Variables (`input: DeleteModelRequest!`)

| Field   | Type | Required | Description                                  |
|---------|------|----------|----------------------------------------------|
| `ID`    | `ID` | Yes      | UUID of the model to delete.                 |

```json
{
  "input": {
    "ID": "{{digital_twins_model_id}}"
  }
}
```

## Response

`200 OK` -- `application/json`. No data returned on success.

```json
{ "data": { "DeleteModel": null } }
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation DeleteModel($input: DeleteModelRequest!) {
  DeleteModel(input: $input)
}
```

**Variables**

```json
{
  "input": {
    "ID": "{{digital_twins_model_id}}"
  }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "DeleteModel": null
    }
}
```

---

## Clone a Model

**POST** `{{edgeUrl}}/digital-twins`

# Clone a Model

Creates a deep copy of an existing model: static attributes, dynamic attributes, transformations, parameters, and hierarchy are all duplicated. **Instances are not cloned** -- the new model starts with no instances.

Use this to template new asset types from an existing definition without re-entering the schema.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation CloneModel($input: CloneModelRequest!) {
  CloneModel(input: $input) {
    ID
    Assets
    Name
    Description
  }
}
```

### Variables (`input: CloneModelRequest!`)

| Field   | Type     | Required | Description                                                            |
|---------|----------|----------|------------------------------------------------------------------------|
| `ID`    | `ID`     | Yes      | UUID of the source model to clone.                                     |
| `Name`  | `String` | Yes      | Display name for the new model. Must be unique across models.          |

```json
{
  "input": {
    "ID": "{{digital_twins_model_id}}",
    "Name": "{{digital_twins_clone}}"
  }
}
```

## Response

`200 OK` -- `application/json`

| Field                           | Type    | Description                                       |
|---------------------------------|---------|---------------------------------------------------|
| `data.CloneModel.ID`            | `ID`    | UUID of the newly created clone.                  |
| `data.CloneModel.Assets`        | `String`| Asset category copied from the source.            |
| `data.CloneModel.Name`          | `String`| Name as supplied.                                 |
| `data.CloneModel.Description`   | `String`| Description copied from the source.               |

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation CloneModel ($input: CloneModelRequest!) {
    CloneModel(input: $input) {
        ID
        Assets
        Name
        Description
    }
}

```

**Variables**

```json
{
    "input": {
        "ID": "{{digital_twins_model_id}}",
        "Name": "{{digital_twins_clone}}"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "CloneModel": {
            "ID": "46946ABE-2235-4B27-833D-CC335DDFCB4C",
            "Assets": "ASSET",
            "Name": "apiTest2",
            "Description": "Digital Twin example 1"
        }
    }
}
```

---

## Export Model

**POST** `{{edgeUrl}}/digital-twins`

# Export Model

Serializes a model and its full schema (static attributes, dynamic attributes, transformations, hierarchy, image) to a JSON document suitable for `Import Model` on another device. Use this to move models between devices, source-control them, or take backups.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation ExportModel($input: ExportModelRequest!) {
  ExportModel(input: $input)
}
```

### Variables (`input: ExportModelRequest!`)

| Field      | Type | Required | Description                                  |
|------------|------|----------|----------------------------------------------|
| `ModelID`  | `ID` | Yes      | UUID of the model to export.                 |

```json
{
  "input": {
    "ModelID": "{{digital_twins_model_id}}"
  }
}
```

## Response

`200 OK` -- `application/json`. `data.ExportModel` is an array of model documents (typically of length 1) containing every nested entity. Each document includes:

- `id`, `name`, `description`, `assets`, `image`
- `dynamicAttributes[]`, `staticAttributes[]`, `transformations[]`
- `hierarchy[]`, `parameters[]`

Persist the response verbatim for re-import.

```json
{
  "data": {
    "ExportModel": [
      {
        "id": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
        "assets": "ASSET",
        "name": "apiTest",
        "description": "updated model",
        "image": "",
        "dynamicAttributes": [
          {
            "id": "31a6eddc-a455-4126-981c-0a6313915a89",
            "modelId": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
            "parentId": "00000000-0000-0000-0000-000000000000"
          }
        ]
      }
    ]
  }
}
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation ExportModel ($input: ExportModelRequest!) {
    ExportModel(input: $input)
}

```

**Variables**

```json
{
    "input": {
        "ModelID": "{{digital_twins_model_id}}"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "ExportModel": [
            {
                "id": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                "assets": "ASSET",
                "name": "apiTest",
                "description": "updated model",
                "image": "",
                "dynamicAttributes": [
                    {
                        "id": "31a6eddc-a455-4126-981c-0a6313915a89",
                        "modelId": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                        "parentId": "00000000-0000-0000-0000-000000000000",
                        "instanceId": "00000000-0000-0000-0000-000000000000",
                        "topic": "",
                        "name": "Speed",
                        "unit": "kmph",
                        "dataType": "string",
                        "schemaId": "30d86400-d103-4c5c-95e4-ab6f02fb00ee"
                    }
                ],
                "staticAttributes": [
                    {
                        "id": "189edb70-1029-4ebf-ab40-27e5423c12ce",
                        "parentId": "00000000-0000-0000-0000-000000000000",
                        "modelId": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                        "instanceId": "00000000-0000-0000-0000-000000000000",
                        "key": "Manufacturer",
                        "value": "API Manufacturer"
                    },
                    {
                        "id": "3f44c811-d8cc-4735-b337-a0f30826641e",
                        "parentId": "00000000-0000-0000-0000-000000000000",
                        "modelId": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                        "instanceId": "00000000-0000-0000-0000-000000000000",
                        "key": "Model",
                        "value": "New API Model"
                    },
                    {
                        "id": "3af47815-bb4f-4288-84da-02b259a75edb",
                        "parentId": "00000000-0000-0000-0000-000000000000",
                        "modelId": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                        "instanceId": "00000000-0000-0000-0000-000000000000",
                        "key": "Model1",
                        "value": "New API Model1"
                    }
                ],
                "hierarchyNodes": [
                    {
                        "id": "60c1f5d0-25ab-4bc0-93a8-52a60a7087d0",
                        "modelId": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                        "position": 0,
                        "parentId": "4c224618-4e24-4bf7-af8b-d87188fdc413",
                        "name": "Model",
                        "isFolder": false,
                        "attributeId": "3f44c811-d8cc-4735-b337-a0f30826641e",
                        "attributeType": "static",
                        "CreatedAt": "2023-11-29T00:54:35.953112543Z"
                    },
                    {
                        "id": "81d4226b-12f9-4d89-957d-2f01a8e5bf12",
                        "modelId": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                        "position": 0,
                        "parentId": "a6a8cb25-ba33-41ca-8d2c-6f657949b849",
                        "name": "Model",
                        "isFolder": false,
                        "attributeId": "3f44c811-d8cc-4735-b337-a0f30826641e",
                        "attributeType": "static",
                        "CreatedAt": "2023-11-29T00:54:35.954122264Z"
                    },
                    {
                        "id": "997ed034-52de-4ce4-9f7d-731294f70e6a",
                        "modelId": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                        "position": 0,
                        "parentId": null,
                        "name": "Manufacturer",
                        "isFolder": false,
                        "attributeId": "189edb70-1029-4ebf-ab40-27e5423c12ce",
                        "attributeType": "static",
                        "CreatedAt": "2023-11-29T00:54:35.951975424Z"
                    },
                    {
                        "id": "4c224618-4e24-4bf7-af8b-d87188fdc413",
                        "modelId": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                        "position": 1,
                        "parentId": null,
                        "name": "New_Node",
                        "isFolder": true,
                        "attributeId": null,
                        "attributeType": null,
                        "CreatedAt": "2023-11-29T00:54:35.952255779Z"
                    },
                    {
                        "id": "a6a8cb25-ba33-41ca-8d2c-6f657949b849",
                        "modelId": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                        "position": 2,
                        "parentId": "4c224618-4e24-4bf7-af8b-d87188fdc413",
                        "name": "New_Node",
                        "isFolder": true,
                        "attributeId": null,
                        "attributeType": null,
                        "CreatedAt": "2023-11-29T00:54:35.953579776Z"
                    }
                ],
                "transformationBackup": [
                    {
                        "ID": "30d86400-d103-4c5c-95e4-ab6f02fb00ee",
                        "ModelID": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
                        "Name": "Transformation",
                        "Schema": "{\n  \"key\": \"value\"\n}"
                    }
                ]
            }
        ]
    }
}
```

---

## Import Model

**POST** `{{edgeUrl}}/digital-twins`

# Import Model

Imports one or more model documents previously produced by `Export Model` (or hand-authored to the same shape). The mutation accepts the document verbatim -- pass an array of model objects.

Imported models keep their original UUIDs from the source export. If a matching ID already exists on the device, the import fails -- delete or clone the existing one first.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation ImportModel($input: Any!) {
  ImportModel(input: $input)
}
```

The `Any!` scalar accepts the same JSON structure that `Export Model` returns. The example below imports a single, minimal model:

```json
{
  "input": [
    {
      "id": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
      "assets": "ASSET",
      "name": "apiImportModel",
      "description": "updated model",
      "image": "",
      "dynamicAttributes": [],
      "staticAttributes": []
    }
  ]
}
```

## Response

`200 OK` -- `application/json`

| Field                          | Type      | Description                                |
|--------------------------------|-----------|--------------------------------------------|
| `data.ImportModel`             | `Boolean` | `true` on success.                         |

```json
{ "data": { "ImportModel": true } }
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation ImportModel($input: Any!) {
    ImportModel(input: $input)
}
```

**Variables**

```json
{
    "input": [
      {
        "id": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
        "assets": "ASSET",
        "name": "apiImportModel",
        "description": "updated model",
        "image": "",
        "dynamicAttributes": [],
        "staticAttributes": [
          {
            "id": "189edb70-1029-4ebf-ab40-27e5423c12ce",
            "parentId": "00000000-0000-0000-0000-000000000000",
            "modelId": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
            "instanceId": "00000000-0000-0000-0000-000000000000",
            "key": "Manufacturer",
            "value": "API Manufacturer"
          }
        ],
        "hierarchyNodes": [],
        "transformationBackup": [
          {
            "ID": "30d86400-d103-4c5c-95e4-ab6f02fb00ee",
            "ModelID": "3a923971-5dfc-4633-b5af-a6bf8c0b60e9",
            "Name": "Transformation",
            "Schema": "{\n  \"key\": \"value\"\n}"
          }
        ]
      }
    ]
  }
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "ImportModel": true
    }
}
```

---

## Upgrade Instances

**POST** `{{edgeUrl}}/digital-twins`

# Upgrade Instances

Reconciles one or more instances with the **current** state of their parent model. Use this after editing a model's schema (adding/removing attributes, changing hierarchy) so that existing instances pick up the change without being deleted and recreated.

Without `Upgrade Instances`, existing instances continue to operate against the model schema they were created against.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation UpgradeInstances($input: [UUID!]) {
  UpgradeInstances(input: $input)
}
```

### Variables

| Field    | Type      | Required | Description                                                       |
|----------|-----------|----------|-------------------------------------------------------------------|
| `input`  | `[UUID!]` | Yes      | Instance UUIDs to upgrade. Pass a single-element array for one.   |

```json
{
  "input": [
    "{{digital_twins_instance_id}}"
  ]
}
```

## Response

`200 OK` -- `application/json`

| Field                          | Type      | Description                                |
|--------------------------------|-----------|--------------------------------------------|
| `data.UpgradeInstances`        | `Boolean` | `true` on success.                         |

```json
{ "data": { "UpgradeInstances": true } }
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation UpgradeInstances($input: [UUID!]) {
    UpgradeInstances(input: $input)
}
```

**Variables**

```json
{
    "input": [
        "{{digital_twins_instance_id}}"
    ]
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "UpgradeInstances": true
    }
}
```

---

## List Static Attributes

**POST** `{{edgeUrl}}/digital-twins`

# List Static Attributes (instance scope)

Identical request shape to `Models > Static Attributes > List Static Attributes` -- the only difference is that an instance scope is expected: pass `ModelID` plus the `InstanceID` of the instance whose overrides you want to see. The response includes the model-level defaults plus any per-instance overrides.

For the model-level list (no overrides), pass `InstanceID: null`. See the Models-side endpoint for the full field reference.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
query ListStaticAttributes($input: ListStaticAttributeRequest!) {
  ListStaticAttributes(input: $input) {
    ID
    ModelID
    InstanceID
    Key
    Value
    CreatedAt
    UpdatedAt
  }
}
```

### Variables

| Field         | Type | Required | Description                                                  |
|---------------|------|----------|--------------------------------------------------------------|
| `ModelID`     | `ID` | Yes      | UUID of the parent model.                                    |
| `InstanceID`  | `ID` | No       | UUID of the instance. Pass `null` for the model-level view.  |

```json
{
  "input": {
    "ModelID": "{{digital_twins_model_id}}",
    "InstanceID": null
  }
}
```

## Response

Same shape as `Models > Static Attributes > List Static Attributes`.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
query ListStaticAttributes($input:ListStaticAttributeRequest!) {
    ListStaticAttributes(input: $input) {
        ID
        ModelID
        InstanceID
        Key
        Value
        CreatedAt
        UpdatedAt
    }
}

```

**Variables**

```json
{
    "input": {
        "ModelID": "{{digital_twins_model_id}}", 
        "InstanceID": null
    }
}

```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "ListStaticAttributes": [
            {
                "ID": "189EDB70-1029-4EBF-AB40-27E5423C12CE",
                "ModelID": "3A923971-5DFC-4633-B5AF-A6BF8C0B60E9",
                "InstanceID": "00000000-0000-0000-0000-000000000000",
                "Key": "Manufacturer",
                "Value": "API Manufacturer",
                "CreatedAt": "2023-08-16T00:03:50.174589529Z",
                "UpdatedAt": "2023-08-16T00:03:55.264377178Z"
            }
        ]
    }
}
```

---

## Update Static Attribute

**POST** `{{edgeUrl}}/digital-twins`

# Update Static Attribute (instance scope)

Identical to `Models > Static Attributes > Update Static Attribute`. The endpoint is reused for both model-level and instance-level updates -- when the static attribute UUID belongs to an instance override, the override is what's written; otherwise the model-level definition is updated.

See the Models-side endpoint for the request shape and field reference.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation UpdateStaticAttribute($input: UpdateStaticAttributeRequest!) {
  UpdateStaticAttribute(input: $input) {
    ID
    ModelID
    InstanceID
    Key
    Value
    CreatedAt
    UpdatedAt
  }
}
```

### Variables

| Field     | Type     | Required | Description                                            |
|-----------|----------|----------|--------------------------------------------------------|
| `ID`      | `ID`     | Yes      | UUID of the static attribute (model or instance override). |
| `ModelID` | `ID`     | Yes      | UUID of the parent model.                              |
| `Key`     | `String` | Yes      | New attribute name.                                    |
| `Value`   | `String` | Yes      | New attribute value.                                   |

```json
{
  "input": {
    "ID": "{{digital_twins_static_attribute_id}}",
    "ModelID": "{{digital_twins_model_id}}",
    "Key": "Model",
    "Value": "Updated Value API Model"
  }
}
```

## Response

Same shape as `Models > Static Attributes > Update Static Attribute`.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation UpdateStaticAttribute($input:UpdateStaticAttributeRequest!) {
    UpdateStaticAttribute(input: $input) {
        ID
        ModelID
        InstanceID
        Key
        Value
        CreatedAt
        UpdatedAt
    }
}

```

**Variables**

```json
{
    "input": {
        "ID": "{{digital_twins_static_attribute_id}}",
        "ModelID": "{{digital_twins_model_id}}",
        "Key": "Model",
        "Value": "Updated Value API Model"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "UpdateStaticAttribute": {
            "ID": "63E4BD05-CE85-41CD-99A6-F69CCD171240",
            "ModelID": "3A923971-5DFC-4633-B5AF-A6BF8C0B60E9",
            "InstanceID": "00000000-0000-0000-0000-000000000000",
            "Key": "Model",
            "Value": "Updated Value API Model",
            "CreatedAt": "2023-08-16T00:13:38.224123983Z",
            "UpdatedAt": "2023-08-16T00:23:12.872714078Z"
        }
    }
}
```

---

## List all Dynamic Attributes

**POST** `{{edgeUrl}}/digital-twins`

# List all Dynamic Attributes (instance scope)

Same shape as `Models > Dynamic Attribute > List all Dynamic Attributes` -- the endpoint is reused. Pass the parent `ModelID` to scope. The response includes per-instance overrides if `InstanceID` is non-zero on the returned records.

See the Models-side endpoint for the full field reference.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
query ListDynamicAttributes($input: ListDynamicAttributeRequest!) {
  ListDynamicAttributes(input: $input) {
    ID
    ModelID
    InstanceID
    Topic
    Name
    Unit
    DataType
    SchemaID
    CreatedAt
    UpdatedAt
  }
}
```

### Variables

| Field      | Type | Required | Description                                  |
|------------|------|----------|----------------------------------------------|
| `ModelID`  | `ID` | Yes      | UUID of the parent model.                    |

```json
{
  "input": {
    "ModelID": "{{digital_twins_model_id}}"
  }
}
```

## Response

Same shape as `Models > Dynamic Attribute > List all Dynamic Attributes`.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
query ListDynamicAttributes ($input: ListDynamicAttributeRequest!) {
    ListDynamicAttributes(input:$input) {
        ID
        ModelID
        InstanceID
        Topic
        Name
        Unit
        DataType
        SchemaID
        CreatedAt
        UpdatedAt
    }
}

```

**Variables**

```json
{
    "input": {
        "ModelID": "{{digital_twins_model_id}}"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "ListDynamicAttributes": [
            {
                "ID": "77990102-3613-4D2B-ABFC-EE761383E631",
                "ModelID": "3A923971-5DFC-4633-B5AF-A6BF8C0B60E9",
                "InstanceID": "00000000-0000-0000-0000-000000000000",
                "Topic": "",
                "Name": "Dynamic attribute 1",
                "Unit": "test1",
                "DataType": "Dictionary",
                "SchemaID": "30D86400-D103-4C5C-95E4-AB6F02FB00EE",
                "CreatedAt": "2023-08-16T19:13:13.808757635Z",
                "UpdatedAt": "2023-08-16T20:17:31.074886398Z"
            }
        ]
    }
}
```

---

## Update Dynamic Attribute

**POST** `{{edgeUrl}}/digital-twins`

# Update Dynamic Attribute (instance scope)

Same mutation as `Models > Dynamic Attribute > Update Dynamic Attribute`. The endpoint is reused for model-level and instance-level updates; pass the override's `ID` together with `ModelID`. To bind a topic to an instance's dynamic attribute, prefer the transactional endpoint `Save Instance/Add Topic to Dynamic Attribute`, which can update many overrides at once.

See the Models-side endpoint for the request shape and field reference.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation UpdateDynamicAttribute($input: UpdateDynamicAttributeRequest!) {
  UpdateDynamicAttribute(input: $input) {
    ID
    ModelID
    InstanceID
    Topic
    Name
    Unit
    DataType
    SchemaID
    CreatedAt
    UpdatedAt
  }
}
```

### Variables

| Field      | Type     | Required | Description                                                                 |
|------------|----------|----------|-----------------------------------------------------------------------------|
| `ID`       | `ID`     | Yes      | UUID of the dynamic attribute (model or instance override).                 |
| `ModelID`  | `ID`     | Yes      | UUID of the parent model.                                                   |
| `Name`/`Unit`/`DataType`/`SchemaID`/`Topic` | mixed | mixed | New values; see `Models > Dynamic Attribute > Update Dynamic Attribute`. |

```json
{
  "input": {
    "ID": "{{digital_twins_dynamic_attribute_id}}",
    "ModelID": "{{digital_twins_model_id}}",
    "Name": "changedName",
    "Unit": "changedUnit",
    "DataType": "string",
    "SchemaID": "30D86400-D103-4C5C-95E4-AB6F02FB00EE",
    "Topic": ""
  }
}
```

## Response

Same shape as `Models > Dynamic Attribute > Update Dynamic Attribute`.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation UpdateDynamicAttribute ($input: UpdateDynamicAttributeRequest!) {
    UpdateDynamicAttribute(input: $input) {
        ID
        ModelID
        InstanceID
        Topic
        Name
        Unit
        DataType
        SchemaID
        CreatedAt
        UpdatedAt
    }
}
```

**Variables**

```json
{
    "input": {
        "ID": "{{digital_twins_dynamic_attribute_id}}",
        "ModelID": {{digital_twins_model_id}},
        "Name": "changedName",
        "Unit": "changedUnit",
        "DataType": "string",
        "SchemaID": "30D86400-D103-4C5C-95E4-AB6F02FB00EE",
        "Topic": ""
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "UpdateDynamicAttribute": {
            "ID": "77990102-3613-4D2B-ABFC-EE761383E631",
            "ModelID": "3A923971-5DFC-4633-B5AF-A6BF8C0B60E9",
            "InstanceID": "00000000-0000-0000-0000-000000000000",
            "Topic": "",
            "Name": "changedName",
            "Unit": "changedUnit",
            "DataType": "string",
            "SchemaID": "30D86400-D103-4C5C-95E4-AB6F02FB00EE",
            "CreatedAt": "2023-08-16T19:13:13.808757635Z",
            "UpdatedAt": "2023-08-16T20:27:34.892193102Z"
        }
    }
}
```

---

## Upload Dynamic Attribute CSV

**POST** `{{edgeUrl}}/digital-twins`

# Upload Dynamic Attribute CSV (instance scope)

Same multipart mutation as `Models > Dynamic Attribute > Upload Dynamic Attribute CSV`. The endpoint is reused; the difference is that the CSV typically contains per-instance overrides (with `InstanceID` set).

See the Models-side endpoint for the form-data shape, CSV format, and response field reference.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body (form-data parts)

| Form key      | Type    | Description                                                            |
|---------------|---------|------------------------------------------------------------------------|
| `operations`  | `text`  | GraphQL envelope, see Models-side endpoint.                            |
| `map`         | `text`  | `{"1": ["variables.input.File"]}`                                      |
| `1`           | `file`  | The CSV file.                                                          |

The `operations` value uses `$input.ModelID` from the parent model -- the service applies overrides to the appropriate instance based on rows in the CSV.

## Response

Same shape as `Models > Dynamic Attribute > Upload Dynamic Attribute CSV`.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

### Response

**Status**: 200 OK

```json
{
    "data": {
        "UploadDynamicAttributesCSV": {
            "Errors": [
                {
                    "Line": null,
                    "Column": null,
                    "Error": "created: 0 updated: 1 skipped: 0",
                    "__typename": "CSVError"
                }
            ],
            "__typename": "UploadCSVResponse"
        }
    }
}
```

---

## List Parameters

**POST** `{{edgeUrl}}/digital-twins`

# List Parameters (instance scope)

Same query as `Models > Parameters > List Parameters` -- pass the instance UUID as `OwnerID` to scope to the instance. See the Models-side endpoint for the request shape and field reference.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
query ListParameters($input: ListParametersRequest) {
  ListParameters(input: $input) {
    ID
    Name
    Value
  }
}
```

### Variables

| Field      | Type | Required | Description                                  |
|------------|------|----------|----------------------------------------------|
| `OwnerID`  | `ID` | Yes      | UUID of the instance.                        |

```json
{
  "input": {
    "OwnerID": "{{digital_twins_instance_id}}"
  }
}
```

## Response

Same shape as `Models > Parameters > List Parameters`.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
query ListParameters($input: ListParametersRequest) {
  ListParameters(input: $input) {
    ID
    Name
    Value
  }
}
```

**Variables**

```json
{
    "input": {
        "OwnerID": "{{digital_twins_instance_id}}"
    }
}
```

### Response

**Status**: 200 OK

```Text
{
    "data": {
        "ListParameters": [
            {
                "ID": "51D2E713-6150-4800-8779-9A1825BE19D7",
                "Name": "Updated key",
                "Value": "Updated value"
            },
            {
                "ID": "8FA45411-ECA0-44C0-BD2F-60341D55C581",
                "Name": "key1",
                "Value": "value1"
            }
        ]
    }
}
```

---

## Get Instances

**POST** `{{edgeUrl}}/digital-twins`

# Get Instances

Lists every instance across every model on the device. Pass an empty input object (`{}`) to return all instances; pass `{ ModelID: "..." }` to filter to one model.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
query ListInstances($input: ListInstancesRequest) {
  ListInstances(input: $input) {
    ID
    ModelID
    Name
    Topic
    Interval
    FlatHierarchy
    __typename
  }
}
```

### Variables

| Field      | Type | Required | Description                                                                |
|------------|------|----------|----------------------------------------------------------------------------|
| `ModelID`  | `ID` | No       | UUID of a model to scope to. Omit to return every instance.                |

```json
{
  "input": {}
}
```

## Response

`200 OK` -- `application/json`

| Field                                | Type           | Description                                                                |
|--------------------------------------|----------------|----------------------------------------------------------------------------|
| `data.ListInstances`                 | `[Instance!]!` | Matching instances.                                                        |
| `data.ListInstances[].ID`            | `ID`           | Instance UUID.                                                             |
| `data.ListInstances[].ModelID`       | `ID`           | UUID of the parent model.                                                  |
| `data.ListInstances[].Name`          | `String`       | Instance display name. Unique across instances.                            |
| `data.ListInstances[].Topic`         | `String`       | MQTT topic the instance publishes to (e.g. `digitaltwins_<Name>`).         |
| `data.ListInstances[].Interval`      | `Int`          | Publish interval in seconds (`1` = once per second).                       |
| `data.ListInstances[].FlatHierarchy` | `Boolean`      | `true` if the instance publishes a flat key-value payload instead of the nested hierarchy. |

```json
{
  "data": {
    "ListInstances": [
      {
        "ID": "D0C8844D-EF72-4933-BB5E-B74372B33F4B",
        "ModelID": "D9117AE5-3F3E-4436-9A2A-6DD725FBD8FB",
        "Name": "Instance1",
        "Topic": "digitaltwins_Instance1",
        "Interval": 1,
        "FlatHierarchy": false
      }
    ]
  }
}
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
query ListInstances($input: ListInstancesRequest) {
    ListInstances(input: $input) {
        ID
        ModelID
        Name
        Topic
        Interval
        FlatHierarchy
        __typename
    }
}
```

**Variables**

```json
{
    "input": {}
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "ListInstances": [
            {
                "ID": "D0C8844D-EF72-4933-BB5E-B74372B33F4B",
                "ModelID": "D9117AE5-3F3E-4436-9A2A-6DD725FBD8FB",
                "Name": "Instance1",
                "Topic": "digitaltwins_Instance1",
                "Interval": 1,
                "FlatHierarchy": false,
                "__typename": "Instance"
            }
        ]
    }
}
```

---

## Create Instance

**POST** `{{edgeUrl}}/digital-twins`

# Create Instance

Creates a new instance of a model. The instance is **disabled** by default (use `Set Instance State` to enable). Its dynamic attributes start with empty `Topic`s -- use `Save Instance/Add Topic to Dynamic Attribute` to bind them to live data sources.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation CreateInstance($input: CreateInstanceRequest!) {
  CreateInstance(input: $input) {
    ID
    ModelID
    Name
    Topic
    Interval
    FlatHierarchy
  }
}
```

### Variables

| Field           | Type     | Required | Description                                                                       |
|-----------------|----------|----------|-----------------------------------------------------------------------------------|
| `ModelID`       | `ID`     | Yes      | UUID of the parent model.                                                         |
| `Name`          | `String` | Yes      | Instance display name. Must be unique across instances.                           |
| `Interval`      | `Int`    | Yes      | Publish interval in seconds.                                                      |
| `Topic`         | `String` | Yes      | MQTT topic to publish under. Conventionally `digitaltwins_<Name>`.                 |
| `FlatHierarchy` | `Boolean`| No       | Default `false`. `true` to publish a flat payload instead of the nested hierarchy. |

```json
{
  "input": {
    "ModelID": "{{digital_twins_model_id}}",
    "Name": "{{digital_twins_instance}}",
    "Interval": 1,
    "Topic": "digitaltwins_{{digital_twins_instance}}",
    "FlatHierarchy": false
  }
}
```

## Response

`200 OK` -- `application/json`. Returns the new instance.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation CreateInstance($input: CreateInstanceRequest!) {
    CreateInstance(input: $input) {
        ID
        ModelID
        Name
        Topic
        Interval
        FlatHierarchy
    }
}
```

**Variables**

```json
{
    "input": {
        "ModelID": "{{digital_twins_model_id}}",
        "Name": "{{digital_twins_instance}}",
        "Interval": 1,
        "Topic": "digitaltwins_{{digital_twins_instance}}",
        "FlatHierarchy": false
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "CreateInstance": {
            "ID": "AB89BC5A-3E72-4CFD-B5FA-3F216BB33601",
            "ModelID": "D9117AE5-3F3E-4436-9A2A-6DD725FBD8FB",
            "Name": "apiInstance",
            "Topic": "digitaltwins_apiInstance",
            "Interval": 1,
            "FlatHierarchy": false
        }
    }
}
```

---

## Delete Instance

**POST** `{{edgeUrl}}/digital-twins`

# Delete Instance

Deletes an instance and all of its per-instance attribute overrides. The parent model is not affected.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation DeleteInstance($input: DeleteInstanceRequest!) {
  DeleteInstance(input: $input)
}
```

### Variables

| Field   | Type | Required | Description                                  |
|---------|------|----------|----------------------------------------------|
| `ID`    | `ID` | Yes      | UUID of the instance to delete.              |

```json
{
  "input": {
    "ID": "{{digital_twins_instance_id}}"
  }
}
```

## Response

`200 OK` -- `application/json`. No data returned on success.

```json
{ "data": { "DeleteInstance": null } }
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation DeleteInstance ($input: DeleteInstanceRequest!) {
    DeleteInstance(input:$input)
}
```

**Variables**

```json
{
    "input": {
        "ID":"AB89BC5A-3E72-4CFD-B5FA-3F216BB33601"
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "DeleteInstance": null
    }
}
```

---

## Update Instance

**POST** `{{edgeUrl}}/digital-twins`

# Update Instance

Updates the top-level fields of an instance (`Name`, `Topic`, `Interval`, `FlatHierarchy`). The `ModelID` is not re-assignable -- delete and recreate to change models.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation UpdateInstance($input: UpdateInstanceRequest!) {
  UpdateInstance(input: $input) {
    ID
    ModelID
    Name
    Topic
    Interval
    FlatHierarchy
  }
}
```

### Variables

Same fields as `Create Instance`, plus:

| Field   | Type | Required | Description                                  |
|---------|------|----------|----------------------------------------------|
| `ID`    | `ID` | Yes      | UUID of the instance to update.              |

```json
{
  "input": {
    "ID": "{{digital_twins_instance_id}}",
    "ModelID": "{{digital_twins_model_id}}",
    "Name": "updated_{{digital_twins_instance}}",
    "Interval": 1,
    "Topic": "digitaltwins_updated_{{digital_twins_instance}}",
    "FlatHierarchy": false
  }
}
```

## Response

`200 OK` -- `application/json`. Returns the updated instance.

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation UpdateInstance($input: UpdateInstanceRequest!) {
    UpdateInstance(input: $input) {
        ID
        ModelID
        Name
        Topic
        Interval
        FlatHierarchy
  }
}
```

**Variables**

```json
{
    "input": {
        "ID": "{{digital_twins_instance_id}}",
        "ModelID": "{{digital_twins_model_id}}",
        "Name": "updated_{{digital_twins_instance}}",
        "Interval": 1,
        "Topic": "digitaltwins_updated_{{digital_twins_instance}}",
        "FlatHierarchy": false
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "UpdateInstance": {
            "ID": "D0C8844D-EF72-4933-BB5E-B74372B33F4B",
            "ModelID": "D9117AE5-3F3E-4436-9A2A-6DD725FBD8FB",
            "Name": "Instance1",
            "Topic": "digitaltwins_Instance1",
            "Interval": 1,
            "FlatHierarchy": false
        }
    }
}
```

---

## Set Instance State

**POST** `{{edgeUrl}}/digital-twins`

# Set Instance State

Enables or disables an instance. A **disabled** instance is configured but does not publish to its topic; **enabled** instances actively run.

Use this to pause/resume publishing without deleting and recreating the instance.

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation SetInstanceState($input: SetInstanceStateRequest!) {
  SetInstanceState(input: $input)
}
```

### Variables

| Field          | Type      | Required | Description                                                                |
|----------------|-----------|----------|----------------------------------------------------------------------------|
| `InstanceID`   | `ID`      | Yes      | UUID of the instance.                                                      |
| `IsEnabled`    | `Boolean` | Yes      | `true` to enable, `false` to disable.                                      |

```json
{
  "input": {
    "InstanceID": "{{digital_twins_instance_id}}",
    "IsEnabled": true
  }
}
```

## Response

`200 OK` -- `application/json`

| Field                          | Type      | Description           |
|--------------------------------|-----------|-----------------------|
| `data.SetInstanceState`        | `Boolean` | `true` on success.    |

```json
{ "data": { "SetInstanceState": true } }
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation SetInstanceState ($input: SetInstanceStateRequest!) {
    SetInstanceState(input: $input)
}

```

**Variables**

```json
{
    "input": {
        "InstanceID": "{{digital_twins_instance_id}}", 
        "IsEnabled": true
    }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "SetInstanceState": true
    }
}
```

---

## Save Instance/ Add Topic to Dynamic Attribute

**POST** `{{edgeUrl}}/digital-twins/`

# Save Instance / Add Topic to Dynamic Attribute (transactional)

Applies one or more updates to an instance's **per-instance** static and dynamic attribute overrides in a single transaction. This is the same shape as `Models > Parameters > Set Parameters`, but scoped to instance overrides instead of model schema.

The most common use case is **adding a topic** to a dynamic attribute override -- this is the step that actually binds the dynamic attribute to a live data source (typically a DeviceHub tag topic).

## Endpoint

```http
POST {{edgeUrl}}/digital-twins
Content-Type: application/json
```

The operation is chosen by the GraphQL query body, not the URL path.
## Authentication

HTTP Basic Auth. **Username** is your API token, **password** is empty. The same token is valid across `/devicehub/v2`, `/analytics/v2`, `/cc`, `/opcua`, and the other LE services.

## Request body

```graphql
mutation TransactionalInstanceUpdate($input: TransactionalInstanceUpdateRequest!) {
  TransactionalInstanceUpdate(input: $input)
}
```

### Variables

The input is an envelope of per-operation arrays. Send only the arrays you need; the others can be empty `Items: []`.

| Field                       | Type           | Description                                                                       |
|-----------------------------|----------------|-----------------------------------------------------------------------------------|
| `updateStaticAttributes`    | `{ Items }`    | List of per-instance static attribute payloads to update.                         |
| `updateDynamicAttributes`   | `{ Items }`    | List of per-instance dynamic attribute payloads to update (e.g. set `Topic`).      |

```json
{
  "input": {
    "updateStaticAttributes":  { "Items": [] },
    "updateDynamicAttributes": {
      "Items": [
        {
          "__typename": "DynamicAttribute",
          "ID": "{{digital_twins_dynamic_attribute_id}}",
          "ModelID": "{{digital_twins_model_id}}",
          "InstanceID": "{{digital_twins_instance_id}}",
          "Topic": "<devicehub-tag-topic>",
          "Name": "Speed",
          "Unit": "kmph",
          "DataType": "string",
          "SchemaID": "30D86400-D103-4C5C-95E4-AB6F02FB00EE"
        }
      ]
    }
  }
}
```

To discover the right `Topic` value for a DeviceHub-backed signal, list the device's registers via `DeviceHub > Tags > List Registers from Single Device` and inspect `Topics[].Topic`.

## Response

`200 OK` -- `application/json`. No data returned on success.

```json
{ "data": { "TransactionalInstanceUpdate": null } }
```

## Errors

GraphQL endpoints return `200 OK` even on logical errors. Inspect the `errors` array in the response body:

```json
{ "errors": [ { "message": "...", "path": ["..."], "extensions": { "code": "..." } } ] }
```

| `extensions.code`       | Meaning                                                          |
|-------------------------|------------------------------------------------------------------|
| `UNAUTHENTICATED`       | Missing or invalid API token.                                    |
| `FORBIDDEN`             | Token lacks permission for this operation.                       |
| `BAD_USER_INPUT`        | Invalid argument (wrong type, missing required field, malformed UUID, unknown ID, etc.). |
| `NOT_FOUND`             | The targeted entity does not exist.                              |
| `INTERNAL_SERVER_ERROR` | DeviceHub fault. Retry, then escalate via `System > Support`.    |

A non-`200` HTTP response means DeviceHub itself is unreachable. See `Dashboard > DeviceHub Status`.

> **TLS note**: edge devices use a self-signed certificate by default. Either install the device CA in your client trust store or disable certificate verification when calling this endpoint directly.


### Request Body

**GraphQL Query**

```graphql
mutation TransactionalInstanceUpdate($input: TransactionalInstanceUpdateRequest!) {
    TransactionalInstanceUpdate(input: $input)
}
```

**Variables**

```json
{
  "input": {
    "updateStaticAttributes": {
      "Items": []
    },
    "updateDynamicAttributes": {
      "Items": [
        {
          "__typename": "DynamicAttribute",
          "ID":"{{digital_twins_dynamic_attribute_id}}",
          "ModelID": "{{digital_twins_model_id}}",
          "InstanceID": "{{digital_twins_instance_id}}",
          "Topic": "devicehub.raw.779F42F3-54FC-4A87-B9A6-CD39CFA4A97D.1A5572B2-8242-40C8-BC63-FFEBC7DAF1E4",
          "Name": "Speed",
          "Unit": "kmph",
          "DataType": "string",
          "AttrDataType": "string",
          "SchemaID": "4C67A3E1-D442-4B39-B0F6-EB07CC200066"
        }
      ]
    }
  }
}
```

### Response

**Status**: 200 OK

```json
{
    "data": {
        "TransactionalInstanceUpdate": null
    }
}
```

---

