# Litmus Unify - UNS

> **Applies to:** Litmus Unify **2.0.x** (current). The version map shows which operations also exist in **<v2.0.0** and how they differ. Full `<v2.0.0` request bodies are in collections.json.

### Version map

| 2.0.x operation | In <v2.0.0? | Difference |
|---|---|---|
| `Namespace` (`namespace`) | Yes -- "Namespace" | None |
| `Namespace Create` (`namespaceCreate`) | Partial -- only via "Rename Namespace" (`NamespaceRename`, a combined delete+create) | 2.0.x adds a standalone create |
| `Namespace Delete` (`namespaceDelete`) | Partial -- only via "Rename Namespace" | 2.0.x adds a standalone delete |
| `Namespace Update Class Config` (`namespaceUpdateClassConfig`) | Yes -- "Update Namespace Rules" (`NamespaceUpdateRules`) | Operation renamed; input type (`NamespaceUpdateClassConfigInput`) is identical |

**Only in <v2.0.0:**

- "Rename Namespace" (`NamespaceRename`) -- in 2.0.x do `Namespace Delete` + `Namespace Create`.
- The `<v2.0.0` "UNS" folder was a grab-bag that also held operations now relocated: `Account` and `Add Permission to Account` -> /luns/mqtt.md (Accounts); `Kafka` -> /luns/integrations.md; `Topics` -> /luns/dashboard.md and /luns/mqtt.md (Clients).

## Table of Contents

- [Namespace](#namespace)
- [Namespace Create](#namespace-create)
- [Namespace Delete](#namespace-delete)
- [Namespace Update Class Config](#namespace-update-class-config)

---

## Namespace

**POST** `{{uns_url}}/mqtt/gql`

# Namespace

Read the current unified namespace: its class path and the allowed values configured for each class. Takes no arguments.
## Endpoint

```http
POST {{uns_url}}/mqtt/gql
```
## Authentication

OAuth2 Bearer token (Keycloak password grant). In Postman the token is inherited from the collection auth; see the collection overview for the raw token request.
## Response

Returns `Namespace!`: `id`, `uns` (the `/`-delimited class path), `classes` (each `{ name, items, minItems, maxItems }`), `filters` (`[String!]`, not a `Filter` object type, which does not exist), and the compiled `hierarchyCompiant` / `hierarchyNonCompiant` node trees (type `Node`).

> **Known API bug**: these two fields are genuinely misspelled in the live schema as `hierarchyCompiant` / `hierarchyNonCompiant` (missing the `l` in "Compliant"). Select them with the misspelling exactly as shown, the correctly spelled names do not exist. Flagged to the broker team.

## Errors

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

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

> **Masked errors**: Litmus Unify masks error detail by default, most failures surface as `{"message": "masked error"}` with `data: null`. The `path` identifies which field failed. Validation failures additionally carry `extensions.code = GRAPHQL_VALIDATION_FAILED` and return HTTP `422`.

A missing or expired Bearer token returns HTTP `401` with the body `bad token`.


### Request Body

**GraphQL Query**

```graphql
query Namespace {
    namespace {
        id
        uns
        classes {
            name
            items
            minItems
            maxItems
        }
        filters
        hierarchyCompiant {
            name
            path
            class
            topics {
                path
                byteCount
                msgCount
                pubRate
                lastSeen
                lastVal {
                    timestamp
                    deviceName
                    tagName
                    datatype
                    value
                }
                clients
                UNS
            }
            clients
        }
        hierarchyNonCompiant {
            name
            path
            class
            topics {
                path
                byteCount
                msgCount
                pubRate
                lastSeen
                lastVal {
                    timestamp
                    deviceName
                    tagName
                    datatype
                    value
                }
                clients
                UNS
            }
            clients
        }
    }
}

```

### Response

**Status**: 200 OK

```json
{
  "data": {
    "namespace": {
      "id": "C5EEF0DC-15C3-4E6A-815E-1F9D497E6BC6",
      "uns": "enterprise/site/area/productionunit",
      "classes": [
        {
          "name": "enterprise",
          "items": [],
          "minItems": 0,
          "maxItems": 1
        },
        {
          "name": "site",
          "items": [],
          "minItems": 0,
          "maxItems": 10
        },
        {
          "name": "area",
          "items": [],
          "minItems": 0,
          "maxItems": 10
        },
        {
          "name": "productionunit",
          "items": [],
          "minItems": 0,
          "maxItems": 0
        }
      ],
      "filters": null,
      "hierarchyCompiant": {
        "name": "",
        "path": "",
        "class": null,
        "topics": [],
        "clients": null
      },
      "hierarchyNonCompiant": {
        "name": "",
        "path": "",
        "class": null,
        "topics": [],
        "clients": null
      }
    }
  }
}
```

---

## Namespace Create

**POST** `{{uns_url}}/mqtt/gql`

# Namespace Create

Create the namespace from a `/`-delimited class path. 2.0.1 supports a single namespace, so this succeeds only when none exists yet.
## Endpoint

```http
POST {{uns_url}}/mqtt/gql
```
## Authentication

OAuth2 Bearer token (Keycloak password grant). In Postman the token is inherited from the collection auth; see the collection overview for the raw token request.
## Arguments

| Field (`NamespaceCreateInput`) | Type | Required | Notes |
|---|---|---|---|
| `uns` | `String!` | yes | Class path, e.g. `enterprise/site/area/productionunit`. |

## Response

Returns `NamespaceCreatePayload` with `root` (the created `Namespace`).
> Calling this while a namespace already exists returns an error.
> The `maxItems` in this create response are transient and read `0` for every class (as the example below shows). The canonical limits -- `enterprise` = 1, `site` = 10, `area` = 10, leaf = 0 -- are returned by the `Namespace` query immediately after. Verified by delete + recreate on the live 2.0.1 broker.
## Errors

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

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

> **Masked errors**: Litmus Unify masks error detail by default, most failures surface as `{"message": "masked error"}` with `data: null`. The `path` identifies which field failed. Validation failures additionally carry `extensions.code = GRAPHQL_VALIDATION_FAILED` and return HTTP `422`.

A missing or expired Bearer token returns HTTP `401` with the body `bad token`.


### Request Body

**GraphQL Query**

```graphql
mutation NamespaceCreate($input: NamespaceCreateInput) {
    namespaceCreate(input: $input) {
        root {
            id
            uns
            classes {
                name
                items
                minItems
                maxItems
            }
        }
    }
}

```

**Variables**

```json
{
  "input": {
    "uns": "enterprise/site/area/productionunit"
  }
}
```

### Response

**Status**: 200 OK

```json
{
  "data": {
    "namespaceCreate": {
      "root": {
        "id": "E29AC386-293C-43B0-BC6F-FECAD340FB1C",
        "uns": "enterprise/site/area/productionunit",
        "classes": [
          {
            "name": "enterprise",
            "items": [],
            "minItems": 0,
            "maxItems": 0
          },
          {
            "name": "site",
            "items": [],
            "minItems": 0,
            "maxItems": 0
          },
          {
            "name": "area",
            "items": [],
            "minItems": 0,
            "maxItems": 0
          },
          {
            "name": "productionunit",
            "items": [],
            "minItems": 0,
            "maxItems": 0
          }
        ]
      }
    }
  }
}
```

---

## Namespace Delete

**POST** `{{uns_url}}/mqtt/gql`

# Namespace Delete

Delete the namespace.
## Endpoint

```http
POST {{uns_url}}/mqtt/gql
```
## Authentication

OAuth2 Bearer token (Keycloak password grant). In Postman the token is inherited from the collection auth; see the collection overview for the raw token request.
## Arguments

| Field (`NamespaceDeleteInput`) | Type | Required | Notes |
|---|---|---|---|
| `id` | `UUID` | no | Ignored in practice, see the warning below. |

## Response

Returns `NamespaceDeletePayload` with the deleted `id`.
> **Destructive**: there is only one namespace, and this removes it regardless of the `id` supplied. Recreate it with `Namespace Create`.
## Errors

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

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

> **Masked errors**: Litmus Unify masks error detail by default, most failures surface as `{"message": "masked error"}` with `data: null`. The `path` identifies which field failed. Validation failures additionally carry `extensions.code = GRAPHQL_VALIDATION_FAILED` and return HTTP `422`.

A missing or expired Bearer token returns HTTP `401` with the body `bad token`.


### Request Body

**GraphQL Query**

```graphql
mutation NamespaceDelete($input: NamespaceDeleteInput) {
    namespaceDelete(input: $input) {
        id
    }
}

```

**Variables**

```json
{
  "input": {
    "id": "C5EEF0DC-15C3-4E6A-815E-1F9D497E6BC6"
  }
}
```

### Response

**Status**: 200 OK

```json
{
  "data": {
    "namespaceDelete": {
      "id": "C5EEF0DC-15C3-4E6A-815E-1F9D497E6BC6"
    }
  }
}
```

---

## Namespace Update Class Config

**POST** `{{uns_url}}/mqtt/gql`

# Namespace Update Class Config

Set the list of allowed values for each configurable namespace class. The `cfg` array is positional over the configurable classes -- every class except the final leaf. `cfg` length must be `<= (number of classes - 1)`; supplying one entry per class including the leaf fails with `class rule validation failed` (verified on 2.0.1). Supplying fewer entries configures a leading prefix (e.g. 2 entries set the first two classes and leave the rest empty). Each entry's items must respect that class's `maxItems` (e.g. `enterprise` has `maxItems = 1`).
## Endpoint

```http
POST {{uns_url}}/mqtt/gql
```
## Authentication

OAuth2 Bearer token (Keycloak password grant). In Postman the token is inherited from the collection auth; see the collection overview for the raw token request.
## Arguments

| Field (`NamespaceUpdateClassConfigInput`) | Type | Required | Notes |
|---|---|---|---|
| `id` | `UUID!` | yes | Namespace id (from `Namespace`). |
| `cfg` | `[NamespaceUpdateClassItem!]!` | yes | One `{ items: [String!]! }` per configurable class (every class except the leaf), in order. Length must be `<= classes - 1`. |

Each class enforces its own `minItems` / `maxItems`. A `cfg` array with one entry per class (including the leaf, which is not configurable) returns `class rule validation failed`; supplying fewer entries configures a leading prefix of the classes.

## Response

Returns `NamespaceUpdateClassConfigPayload` with the updated `classes`.
## Errors

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

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

> **Masked errors**: Litmus Unify masks error detail by default, most failures surface as `{"message": "masked error"}` with `data: null`. The `path` identifies which field failed. Validation failures additionally carry `extensions.code = GRAPHQL_VALIDATION_FAILED` and return HTTP `422`.

A missing or expired Bearer token returns HTTP `401` with the body `bad token`.


### Request Body

**GraphQL Query**

```graphql
mutation NamespaceUpdateClassConfig($input: NamespaceUpdateClassConfigInput) {
    namespaceUpdateClassConfig(input: $input) {
        classes {
            name
            items
            minItems
            maxItems
        }
    }
}

```

**Variables**

```json
{
  "input": {
    "id": "C5EEF0DC-15C3-4E6A-815E-1F9D497E6BC6",
    "cfg": [
      {
        "items": [
          "enterprise-example"
        ]
      },
      {
        "items": [
          "site-example"
        ]
      },
      {
        "items": [
          "area-example"
        ]
      }
    ]
  }
}
```

### Response

**Status**: 200 OK

```json
{
  "data": {
    "namespaceUpdateClassConfig": {
      "classes": [
        {
          "name": "enterprise",
          "items": [
            "enterprise-example"
          ],
          "minItems": 0,
          "maxItems": 1
        },
        {
          "name": "site",
          "items": [
            "site-example"
          ],
          "minItems": 0,
          "maxItems": 10
        },
        {
          "name": "area",
          "items": [
            "area-example"
          ],
          "minItems": 0,
          "maxItems": 10
        },
        {
          "name": "productionunit",
          "items": [],
          "minItems": 0,
          "maxItems": 0
        }
      ]
    }
  }
}
```

---

