{
  "openapi": "3.1.0",
  "info": {
    "title": "DataHub API",
    "description": "# Authentication\n\nAll API endpoints are authenticated with an OAuth2 JWT access token,\nsent in the `Authorization` header with the Bearer format:\n\n```\n\nAuthorization: Bearer <access token>\n\n```\n\nFor applications and scripts, obtain a token with the OAuth2\nclient-credentials grant against your identity provider (one service\naccount per tenant). The token must carry the `organization` claim\nnaming exactly one organization; whether that happens automatically\ndepends on how your realm produces the claim — a protocol mapper on\nthe client emits it on every token, while a realm using Keycloak\nOrganizations (such as the bundled dev realm) only emits it when the\nrequest names a scope like `organization:*`. See the \"Machine-to-machine\ntokens\" section of GETTING_STARTED.md and KEYCLOAK_ORG_GROUPS.md. For\nquick manual experiments you can copy your own signed-in session's\ntoken from the Console under your username (\"copy token\"); note it\nexpires with your session, so it is not suited to anything\nlong-running.\n\n# Error responses\n\nWhen something goes wrong the API always returns JSON in the same shape:\n\n```json\n{ \"error\": { ...details... } }\n```\n\nThe HTTP status code tells you the category; the `error` object tells you\nthe specifics. Each endpoint documents which statuses it can return and\nlinks to the response schema. The common ones are:\n\n- **400 Bad Request** — your input was rejected before anything changed.\n  Body is a `BadRequestError` with a human-readable `message` and a\n  `fields` list saying which inputs were wrong. Fix the inputs and retry.\n- **401 Unauthorized** — your API token is missing or invalid. Check the\n  `Authorization` header.\n- **404 Not Found** — the thing you asked for doesn't exist (wrong `id`\n  or `externalId`, or it belongs to another tenant).\n- **409 Conflict** — two flavours. Either a `DuplicateError` (\"an object\n  with this `externalId` already exists\") — pick a different `externalId`\n  or use the corresponding `/update` endpoint. Or a `ConflictError`\n  (\"the resource was modified or removed by another request\") — re-read\n  the current state and retry.\n- **422 Unprocessable Entity** — input was parseable but a field failed\n  validation (length, allowed characters, required-ness). Response lists\n  the offending fields.\n- **429 Too Many Requests** — you've hit a rate limit. Back off and retry.\n- **5xx** — something went wrong on our side. Safe to retry after a short\n  backoff; if it persists, contact support.\n\nEvery error body is safe to log and show to end users; it never contains\ncredentials or internal stack traces.",
    "license": {
      "name": "AGPL",
      "url": "https://www.gnu.org/licenses/agpl-3.0.en.html"
    },
    "version": "0.3",
    "x-logo": {
      "backgroundColor": "#FFFFFF",
      "altText": "IntelliStream",
      "url": "https://intellistream.ai/static/images/intellistream-logo.svg"
    }
  },
  "servers": [
    {
      "url": "https://api-{project}.intellistream.ai",
      "description": "The url is your api-{your-project-name}.intellistream.ai"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Units",
      "description": "Units are the standard list of measurement units available across DataHub (meters,\ncelsius, litres per second, etc.). When you create a timeseries tied to a physical\nquantity, you pick one of these units by `externalId`. The list is managed centrally —\nthis API is read-only."
    },
    {
      "name": "Policies",
      "description": "Endpoints for creating, updating, listing and deleting Policy nodes."
    },
    {
      "name": "Files",
      "description": "Upload, list, download and delete files organised into folders. Useful for\nattaching documents (manuals, reports, photos) to your resources. The files\nfeature must be enabled for your tenant; calls return `403 Forbidden` if it isn't."
    },
    {
      "name": "Governance",
      "description": "Governance templates describe the compliance rules (retention, access\nrestrictions, required metadata fields) a dataset can be held to. Attach a\ntemplate to a dataset via its policy, then DataHub enforces the rules on\nreads and writes automatically."
    },
    {
      "name": "Data sets",
      "description": "Data sets group and track data by its source. For example, a data set can contain all work orders originating from SAP. Typically, an organization will have one data set for each of its data ingestion pipelines in DataHub"
    },
    {
      "name": "Tenant",
      "description": "Endpoints for reading settings that apply to the tenant your API token belongs to.\nUseful when your client needs to know which optional features are enabled before\nshowing UI for them."
    },
    {
      "name": "Labels",
      "description": "Labels categorise resources and timeseries. Every resource must carry at least one\nlabel (e.g. `PIPE`, `SENSOR`, `DOCUMENT`) so filters and the graph UI can group\nrelated objects together. Labels are tenant-scoped and shared across resources —\ncreating a resource with a new label name auto-creates the label."
    }
  ],
  "paths": {
    "/timeseries": {
      "get": {
        "tags": [
          "Time-series"
        ],
        "summary": "List recent timeseries",
        "description": "List timeseries in your tenant, newest first.\n\nUse `limit` to cap the response (default 100, max 10000). For targeted\nlookups, use `POST /timeseries/byids` or `POST /timeseries/search` instead\n— this endpoint is mainly for browsing.\n\nPass `dataSetId` to list only the timeseries of that dataset **and every\ndataset beneath it** in the dataset hierarchy — a timeseries attached to a\nchild (or grandchild, …) dataset is included. Datasets you lack read access\nto are silently omitted.\n",
        "operationId": "list_2",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of timeseries to return. Must be a positive integer up to 10000.",
            "required": false,
            "schema": {
              "type": "string",
              "default": "1000"
            },
            "example": 1000
          },
          {
            "name": "dataSetId",
            "in": "query",
            "description": "Restrict results to this dataset and every dataset beneath it.",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": 5677892
          }
        ],
        "responses": {
          "200": {
            "description": "Up to `limit` timeseries, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Time Series Collection"
                }
              }
            }
          },
          "400": {
            "description": "`limit` is not a positive integer ≤ 10000, or `dataSetId` is not a number.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "dataSetId must be a number"
                }
              }
            }
          }
        },
        "x-sort": "1"
      }
    },
    "/timeseries/{id}": {
      "get": {
        "tags": [
          "Time-series"
        ],
        "summary": "Get a timeseries by id",
        "description": "Look up one timeseries by its numeric `id`.\n\nA timeseries you may not read is reported as **404**, not 403 — a hidden\ntimeseries must be indistinguishable from a missing one.\n\nTo look one up by `externalId`, or several at once, use `POST /timeseries/byids`.\n",
        "operationId": "get_2",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Numeric id of the timeseries.",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5677892
          }
        ],
        "responses": {
          "200": {
            "description": "The timeseries was found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Time Series Collection"
                }
              }
            }
          },
          "404": {
            "description": "No timeseries with this `id` exists, or you may not read the data set it belongs to.",
            "content": {
              "application/problem+json": {}
            }
          }
        },
        "x-sort": "1"
      }
    },
    "/timeseries/recommend-value-type/{unitExternalId}": {
      "get": {
        "tags": [
          "Time-series"
        ],
        "summary": "Recommend a value type for a unit",
        "description": "Suggest the timeseries `valueType` that gives the best ClickHouse compression for a\nunit of measure, while still representing the data faithfully. Identify the unit by\nits catalogue `externalId` (e.g. `temperature_deg_c`, `pressure_bar`, `pressure_pa`).\n\nThe mapping is heuristic and hard-coded — small low-precision ranges map to\n`DECIMAL32` (exact, 4 bytes, best ratio), wide-magnitude analog values to `FLOAT32`\n(4 bytes, full range). An unknown unit returns a compact default with\n`recognized=false`. Use the result to pre-select the value type when creating a\ntimeseries — it is advice, not a constraint.\n",
        "operationId": "recommendValueType",
        "parameters": [
          {
            "name": "unitExternalId",
            "in": "path",
            "description": "External id of the unit to recommend a value type for.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "temperature:deg_c"
          }
        ],
        "responses": {
          "200": {
            "description": "The recommended value type for the unit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValueTypeRecommendation"
                }
              }
            }
          }
        },
        "x-sort": "1"
      }
    },
    "/timeseries/filter": {
      "post": {
        "tags": [
          "Time-series"
        ],
        "summary": "List timeseries matching filter criteria",
        "description": "Return the timeseries that match a set of filters. All filters are combined\nwith AND — a timeseries must match every filter you supply to be included.\n\nEvery list field also accepts a bare value, so `\"unit\": \"bar\"` and\n`\"unit\": [\"bar\"]` mean the same thing.\n\nSupported filters:\n- `dataSetId` — timeseries of those datasets **and every dataset beneath\n  them** in the dataset hierarchy. Each entry names a dataset by `id` or\n  `externalId`.\n- `id` / `externalId` / `name` / `source` / `labels` — the criteria every\n  node type shares. `externalId`, `name` and `source` are pattern lists:\n  `*` and `%` are wildcards, `_` is literal, matching is case-insensitive, and\n  an entry without a wildcard matches exactly. `labels` must **all** be present.\n- `unit` / `unitExternalId` — pattern lists on the same rules.\n- `valueType` — `BIGINT`, `FLOAT`, `FLOAT32`, `NUMERIC`, `DECIMAL32`, `TEXT`\n  or `MIXED`. Matched exactly (it is a closed catalogue), case-insensitively.\n- `metadata` — every entry must be present. A **null value matches the key\n  alone**, whatever it carries, so `{\"health\": null}` finds anything tagged\n  `health`. This replaced the old `metadataKey`/`metadataValue` pair.\n- `createdTime` / `lastUpdatedTime` — inclusive `min`/`max` instants.\n\nDatasets you lack read access to are silently omitted. Use `limit` to cap the\nresult size (default 1000, max 10000); results come newest created first. For\nfree-text lookups use `POST /timeseries/search` instead.\n\n`sort` takes one property — `id`, `externalId`, `name`, `source`,\n`description`, `createdTime`, `lastUpdatedTime` or `dataSetId` — with `order` of\n`asc` or `desc`; `id` is always appended so the order is total. The response\ncarries `nextCursor` when there may be more: send it back as `cursor`, with the\nsame `sort` it came from, and keep going while it is present. Keyset paging, not\n`OFFSET`, so a deep page costs what a shallow one does.\n",
        "operationId": "filter",
        "requestBody": {
          "description": "Filter criteria and optional limit.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Timeseries Query"
              },
              "example": {
                "limit": 100,
                "filter": {
                  "dataSetId": [
                    {
                      "id": "12"
                    }
                  ],
                  "name": [
                    "RPM*"
                  ],
                  "externalId": [
                    "rpm_pump_*"
                  ],
                  "labels": [
                    "PUMP"
                  ],
                  "unit": [
                    "bar"
                  ],
                  "valueType": [
                    "FLOAT"
                  ],
                  "metadata": {
                    "sensor_vendor": null
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The timeseries that match every supplied filter, newest first. Empty `items[]` means nothing matched.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Time Series Collection"
                }
              }
            }
          },
          "400": {
            "description": "The request failed validation, e.g. `limit` above 10000 or an over-long filter value.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        },
        "x-sort": "2"
      }
    },
    "/timeseries/byids": {
      "post": {
        "tags": [
          "Time-series"
        ],
        "summary": "Find timeseries by id or externalId",
        "description": "Look up several timeseries in one call. Each entry in `items[]` carries\neither a numeric `id`, an `externalId`, or both — mix freely.\n\nTimeseries that don't exist are silently omitted from the response; compare\nthe returned items against what you asked for to detect missing ones.\n",
        "operationId": "findByIdList",
        "requestBody": {
          "description": "Identifiers of the timeseries to look up.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "id": 5677892
                  },
                  {
                    "externalId": "sensor_temp_room_a"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The timeseries that were found. Missing ones are silently left out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Time Series Collection"
                }
              }
            }
          }
        },
        "x-sort": "2"
      }
    },
    "/timeseries/create": {
      "post": {
        "tags": [
          "Time-series"
        ],
        "summary": "Create timeseries",
        "description": "Create one or more **timeseries** — containers that hold a sequence of\ntimestamped values (temperature readings, flow rates, status codes…).\n\nEach timeseries needs:\n- a unique `externalId` within your tenant,\n- a `name`,\n- a `valueType` — `BIGINT`, `FLOAT`, `FLOAT32`, `NUMERIC`, `DECIMAL32`,\n  `TEXT`, or `MIXED` — which fixes what kinds of values you can write to it later,\n- optional `unit` / `unitExternalId` if you're tracking a physical quantity,\n- optional `dataSetId` to group it with related data.\n\n### All-or-nothing\nIf any timeseries in the request fails validation, none are created.\n",
        "operationId": "create",
        "requestBody": {
          "description": "Timeseries to create.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Time Series Collection"
              },
              "examples": {
                "Temperature sensor": {
                  "description": "Temperature sensor",
                  "value": {
                    "items": [
                      {
                        "externalId": "sensor_temp_room_a",
                        "name": "Room A temperature",
                        "description": "Ambient temp, pump room A",
                        "valueType": "FLOAT",
                        "unit": "celsius",
                        "dataSetId": 12,
                        "labels": [
                          "SENSOR"
                        ],
                        "metadata": {
                          "location": "rack-12"
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "The created timeseries, with server-assigned `id`s you can use in later calls.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Time Series Collection"
                }
              }
            }
          },
          "400": {
            "description": "The request has a problem the server spotted before saving. Typical causes: missing required field, invalid `valueType`, referenced `dataSetId` doesn't exist, unknown `unit`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          },
          "409": {
            "description": "A timeseries with one of the `externalId`s already exists. The `duplicated` list tells you which ones. Pick a different `externalId`, or use `POST /timeseries/update` to modify the existing timeseries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DuplicateError"
                }
              }
            }
          },
          "422": {
            "description": "One or more fields failed validation rules. Response lists the offending fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataWrapper"
                }
              }
            }
          }
        },
        "x-sort": "3"
      }
    },
    "/timeseries/update": {
      "post": {
        "tags": [
          "Time-series"
        ],
        "summary": "Update timeseries",
        "description": "Change fields on existing timeseries. Identify each one by `id` or\n`externalId`. Only fields you name in the `update` block are changed.\n\nEach updatable field is an object:\n- `\"set\"` — replace the field with this value.\n- `\"setNull\": true` — clear the field. Rejected with a 400 on `name` and\n  `externalId`, which every timeseries must have; use `set` to change them.\n- `\"add\"` / `\"remove\"` — for `metadata`, add or remove specific keys.\n\n### Value type is fixed\n`valueType` cannot be changed after creation — it determines how stored\ndata-points were encoded. To change type, create a new timeseries.\n\n### All-or-nothing\nA single validation failure rolls back the whole batch.\n",
        "operationId": "update",
        "requestBody": {
          "description": "Timeseries to update. Only fields you name in `update` are changed.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTimeseriesWrapper"
              },
              "examples": {
                "Rename and add metadata": {
                  "description": "Rename and add metadata",
                  "value": {
                    "items": [
                      {
                        "externalId": "sensor_temp_room_a",
                        "update": {
                          "name": {
                            "set": "Room A ambient temp"
                          },
                          "description": {
                            "set": "Primary ambient sensor, verified"
                          },
                          "metadata": {
                            "add": {
                              "calibration_due": "2026-10-01"
                            }
                          }
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The timeseries after the update, with current values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Time Series Collection"
                }
              }
            }
          },
          "400": {
            "description": "The request has a problem the server spotted before saving. Typical causes: neither `id` nor `externalId` supplied, the timeseries doesn't exist, or `set` and `setNull` both present on the same field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          },
          "409": {
            "description": "Conflict. Either the new `externalId` already belongs to another timeseries (`DuplicateError` — pick a different one), or someone else changed the timeseries while your update was in flight (`ConflictError` — re-fetch with `/byids` and retry).",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/DuplicateError"
                    },
                    {
                      "$ref": "#/components/schemas/ConflictError"
                    }
                  ]
                }
              }
            }
          }
        },
        "x-sort": "4"
      }
    },
    "/timeseries/search": {
      "post": {
        "tags": [
          "Time-series"
        ],
        "summary": "Full-text search timeseries",
        "description": "Free-text search across timeseries. The phrase is matched against `name`,\n`externalId` and `description`. Matching is fuzzy and word-aware: search for\n`temp` and you'll also find `temperature`, `tempered`, and multi-word names\ncontaining the term.\n\n### Narrowing the results\n`filter` is optional and takes the same criteria as `POST /timeseries/filter`\n— `unit`, `valueType`, `dataSetId`, metadata, and the rest. It only ever\n*removes* matches; the phrase decides what the candidates are. Omit it for no\nnarrowing.\n\nIf you don't need a phrase at all, use `POST /timeseries/filter`: a structured\nquery on its own is faster and more predictable than one bolted to a search.\n\n`limit` caps the result size (default 100, max 1000).\n\n### Result order\nRanked by relevance (`ts_rank`), strongest match first, with `id` as a\ntie-break so equal-scoring rows keep a stable order and repeated identical\nrequests agree. Ranking means the database scores and sorts every match before\napplying `limit`, so a very broad phrase costs more than a narrow one.\n\nMatching rules may evolve over time; don't rely on this endpoint for equality\ntests — use `POST /timeseries/byids` for that.\n",
        "operationId": "search",
        "requestBody": {
          "description": "Search phrase, optional filter, optional limit.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchBodyTimeseries Query Filter"
              },
              "example": {
                "search": {
                  "query": "temperature"
                },
                "filter": {
                  "unit": "deg_c"
                },
                "limit": 50
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Timeseries ranked by how well they match the search phrase.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Time Series Collection"
                }
              }
            }
          }
        },
        "x-sort": "5"
      }
    },
    "/timeseries/delete": {
      "post": {
        "tags": [
          "Time-series"
        ],
        "summary": "Delete timeseries",
        "description": "Delete one or more timeseries by `id` or `externalId`. Deletes the\ntimeseries definition **and every data-point it contains** — this cannot\nbe undone.\n\nThe definition is gone the moment this call returns; the data-point purge is\nhanded off asynchronously and completes shortly after. The points are already\nunreachable in the meantime, since every read resolves the timeseries first.\n\n### Safety checks\nThe request is rejected with `400 Bad Request` if:\n- any targeted timeseries is the **start** of a relationship — delete those\n  relationships first via `POST /edges/delete`;\n- any targeted timeseries is still bound to one or more **subscriptions** —\n  the response body lists the blocking subscriptions\n  (`subscriptionId`, `subscriptionExternalId`, `timeseriesId`). Remove them\n  via `POST /subscriptions/delete` first, then retry.\n\n### Idempotent\nDeleting a timeseries that's already gone returns `204` and is a no-op.\n",
        "operationId": "delete_10",
        "requestBody": {
          "description": "Identifiers of the timeseries to delete.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "externalId": "sensor_temp_room_a"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The timeseries were deleted and their data-points scheduled for purge. No response body."
          },
          "400": {
            "description": "A safety check failed. Either the timeseries is still linked to other resources via a relationship, or it's still bound to a subscription. Response lists the blocking items so you can clean them up first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                },
                "example": {
                  "error": {
                    "code": 400,
                    "message": "Cannot delete timeseries that are referenced by subscription(s). Remove the subscriptions first.",
                    "fields": [
                      {
                        "type": "subscription",
                        "subscriptionId": "91",
                        "subscriptionExternalId": "fleet_dashboard",
                        "timeseriesId": "5677892"
                      }
                    ]
                  }
                }
              }
            }
          },
          "409": {
            "description": "Concurrency conflict — another request modified or deleted the timeseries between read and write. Clients should re-fetch the current state and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        },
        "x-sort": "6"
      },
      "delete": {
        "tags": [
          "Time-series"
        ],
        "summary": "Delete timeseries",
        "description": "Delete one or more timeseries by `id` or `externalId`. Deletes the\ntimeseries definition **and every data-point it contains** — this cannot\nbe undone.\n\nThe definition is gone the moment this call returns; the data-point purge is\nhanded off asynchronously and completes shortly after. The points are already\nunreachable in the meantime, since every read resolves the timeseries first.\n\n### Safety checks\nThe request is rejected with `400 Bad Request` if:\n- any targeted timeseries is the **start** of a relationship — delete those\n  relationships first via `POST /edges/delete`;\n- any targeted timeseries is still bound to one or more **subscriptions** —\n  the response body lists the blocking subscriptions\n  (`subscriptionId`, `subscriptionExternalId`, `timeseriesId`). Remove them\n  via `POST /subscriptions/delete` first, then retry.\n\n### Idempotent\nDeleting a timeseries that's already gone returns `204` and is a no-op.\n",
        "operationId": "delete_11",
        "requestBody": {
          "description": "Identifiers of the timeseries to delete.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "externalId": "sensor_temp_room_a"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The timeseries were deleted and their data-points scheduled for purge. No response body."
          },
          "400": {
            "description": "A safety check failed. Either the timeseries is still linked to other resources via a relationship, or it's still bound to a subscription. Response lists the blocking items so you can clean them up first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                },
                "example": {
                  "error": {
                    "code": 400,
                    "message": "Cannot delete timeseries that are referenced by subscription(s). Remove the subscriptions first.",
                    "fields": [
                      {
                        "type": "subscription",
                        "subscriptionId": "91",
                        "subscriptionExternalId": "fleet_dashboard",
                        "timeseriesId": "5677892"
                      }
                    ]
                  }
                }
              }
            }
          },
          "409": {
            "description": "Concurrency conflict — another request modified or deleted the timeseries between read and write. Clients should re-fetch the current state and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        },
        "x-sort": "6"
      }
    },
    "/timeseries/data": {
      "post": {
        "tags": [
          "Time-series"
        ],
        "summary": "Insert data-points",
        "description": "Append timestamped values to one or more timeseries.\n\nGroup data-points by target timeseries: each entry in `items[]` names a\ntimeseries (by `id` or `externalId`) and carries its list of `datapoints`.\n`timestamp` is epoch milliseconds (UTC). `value` must match the target\ntimeseries' `valueType`:\n- `BIGINT` — a 64-bit integer as a string (\"42\").\n- `FLOAT` / `NUMERIC` — a number as a string (\"3.14\").\n- `TEXT` — any string.\n\n### Overwrites are deterministic\nIf you insert a data-point whose timestamp already exists, the new value\nreplaces the old one for that timeseries+timestamp. This is intentional —\nidempotent retries are safe.\n\n### Some targets missing\nIf every targeted timeseries exists, the call returns `204 No Content` with\nan empty body. If some don't exist, the data-points for the ones that *do*\nexist are still inserted and the call returns `404 Not Found`, with the\nmissing timeseries listed as per-entry errors in the response body.\n",
        "operationId": "insertDataPoints",
        "requestBody": {
          "description": "Data-points grouped by target timeseries.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DatapointsCollectionDataWrapper"
              },
              "examples": {
                "Two readings for one temperature sensor": {
                  "description": "Two readings for one temperature sensor",
                  "value": {
                    "items": [
                      {
                        "externalId": "sensor_temp_room_a",
                        "datapoints": [
                          {
                            "timestamp": 1745328000000,
                            "value": "22.4"
                          },
                          {
                            "timestamp": 1745328060000,
                            "value": "22.6"
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "All targeted timeseries existed and their data-points were accepted. No response body."
          },
          "404": {
            "description": "One or more targeted timeseries don't exist. The data-points for the timeseries that *do* exist are still inserted; the response body lists the missing ones as per-entry errors, each carrying the offending `externalId`/`id`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          },
          "422": {
            "description": "A value failed to parse against the target timeseries' `valueType` — e.g. text value sent to a `BIGINT` timeseries. Fix the offending entry and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        },
        "x-sort": "7"
      }
    },
    "/timeseries/data/list": {
      "post": {
        "tags": [
          "Time-series"
        ],
        "summary": "Retrieve data-points",
        "description": "Fetch data-points from one or more timeseries, optionally filtered by\ntime range, limit, aggregation, or granularity.\n\nTypical use:\n- Name the target timeseries by `id` or `externalId`.\n- Pick a time window with `start` / `end` (epoch milliseconds). Leave them\n  out to get the most recent `limit` points.\n- Pass an `aggregates` list (`AVG`, `MIN`, `MAX`, `COUNT`, `SUM`) and a\n  `granularity` (`1m`, `1h`, `1d`) to get bucketed summaries instead of raw\n  points — dramatically smaller responses for long windows.\n\n### Cursors for large windows\nIf the server decides the result is too big to return in one response, it\nstreams it via a `cursor`. The response carries a `nextCursor` string; send\nit back in the next request to get the following page. `nextCursor` is\nabsent when there's no more data.\n",
        "operationId": "retrieveDatapoints",
        "requestBody": {
          "description": "Per-timeseries retrieval filters.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DataRetrieverForDatapoints"
              },
              "examples": {
                "Hourly averages for last 24 hours": {
                  "description": "Hourly averages for last 24 hours",
                  "value": {
                    "items": [
                      {
                        "externalId": "sensor_temp_room_a",
                        "start": 1745241600000,
                        "end": 1745328000000,
                        "aggregates": [
                          "AVG",
                          "MAX"
                        ],
                        "granularity": "1h",
                        "limit": 24
                      }
                    ]
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Data-points per requested timeseries. Entries may include `nextCursor` for paginated windows.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DatapointsDataWrapper"
                }
              }
            }
          },
          "400": {
            "description": "Invalid filter — e.g. `granularity` set without `aggregates`, or `start` > `end`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        },
        "x-sort": "8"
      }
    },
    "/timeseries/data/delete": {
      "post": {
        "tags": [
          "Time-series"
        ],
        "summary": "Delete data-points",
        "description": "Remove data-points from one or more timeseries by time range. The\ntimeseries definition itself is left intact — use `POST /timeseries/delete`\nto remove that.\n\nIdentify each target timeseries by `id` or `externalId`, then give the\nwindow to clear as `inclusiveBegin` / `exclusiveEnd`. Both take either\nISO-8601 (`2026-01-01T00:00:00Z`) or epoch milliseconds, and both are\noptional: leave `exclusiveEnd` out to delete everything from\n`inclusiveBegin` onward, or `inclusiveBegin` out to delete everything\nup to `exclusiveEnd`. Leave **both** out and the whole series is cleared,\nwhich is how you empty a series without losing its definition, edges and\nsubscriptions.\n\n### Cannot be undone\nDeleted data-points are gone. Double-check the window before calling this.\n",
        "operationId": "deleteDatapoints",
        "requestBody": {
          "description": "Per-timeseries delete windows.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Delete Datapoint Collection"
              },
              "examples": {
                "Delete one day of readings": {
                  "description": "Delete one day of readings",
                  "value": {
                    "items": [
                      {
                        "externalId": "sensor_temp_room_a",
                        "inclusiveBegin": "1745241600000",
                        "exclusiveEnd": "1745328000000"
                      }
                    ]
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The data-points in the requested windows were removed. No response body."
          },
          "400": {
            "description": "A named timeseries does not exist, or a window bound is neither ISO-8601 nor epoch milliseconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          },
          "500": {
            "description": "The delete couldn't be accepted right now. Safe to retry after a short backoff."
          }
        },
        "x-sort": "9"
      },
      "delete": {
        "tags": [
          "Time-series"
        ],
        "summary": "Delete data-points",
        "description": "Remove data-points from one or more timeseries by time range. The\ntimeseries definition itself is left intact — use `POST /timeseries/delete`\nto remove that.\n\nIdentify each target timeseries by `id` or `externalId`, then give the\nwindow to clear as `inclusiveBegin` / `exclusiveEnd`. Both take either\nISO-8601 (`2026-01-01T00:00:00Z`) or epoch milliseconds, and both are\noptional: leave `exclusiveEnd` out to delete everything from\n`inclusiveBegin` onward, or `inclusiveBegin` out to delete everything\nup to `exclusiveEnd`. Leave **both** out and the whole series is cleared,\nwhich is how you empty a series without losing its definition, edges and\nsubscriptions.\n\n### Cannot be undone\nDeleted data-points are gone. Double-check the window before calling this.\n",
        "operationId": "deleteDatapoints_1",
        "requestBody": {
          "description": "Per-timeseries delete windows.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Delete Datapoint Collection"
              },
              "examples": {
                "Delete one day of readings": {
                  "description": "Delete one day of readings",
                  "value": {
                    "items": [
                      {
                        "externalId": "sensor_temp_room_a",
                        "inclusiveBegin": "1745241600000",
                        "exclusiveEnd": "1745328000000"
                      }
                    ]
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The data-points in the requested windows were removed. No response body."
          },
          "400": {
            "description": "A named timeseries does not exist, or a window bound is neither ISO-8601 nor epoch milliseconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          },
          "500": {
            "description": "The delete couldn't be accepted right now. Safe to retry after a short backoff."
          }
        },
        "x-sort": "9"
      }
    },
    "/timeseries/data/latest": {
      "post": {
        "tags": [
          "Time-series"
        ],
        "summary": "Retrieve the latest data-point per timeseries",
        "description": "For each timeseries you name, return its single most recent data-point\n(the one with the highest `timestamp`). Cheap and fast — ideal for\ndashboards that show \"current value\" widgets.\n\nIf a timeseries has no data-points at all, it's omitted from the response.\n",
        "operationId": "latestDatapoint",
        "requestBody": {
          "description": "Identifiers of the timeseries to fetch the latest point from.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "externalId": "sensor_temp_room_a"
                  },
                  {
                    "externalId": "sensor_flow_main"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The latest data-point per requested timeseries. Empty timeseries are omitted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Timeseries Collection"
                }
              }
            }
          }
        },
        "x-sort": "10"
      }
    },
    "/timeseries/datapoints/subscription/listen/{externalIds}": {
      "get": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Listen to subscription (WebSocket)",
        "description": "> **WebSocket endpoint.** This operation is documented under HTTP for discoverability\n> in Swagger UI, but the underlying protocol is WebSocket (RFC 6455). The client\n> initiates an HTTP/1.1 upgrade; on success the server returns `101 Switching\n> Protocols` and the connection becomes a long-lived duplex channel.\n\n## Purpose\n\nProxies one or more Pulsar consumers over a single WebSocket so clients can stream\ndatapoints from several subscriptions' fan-out topics at once and drive acks/nacks from\ntheir side. The set of subscriptions is seeded from the path and can be changed at\nruntime.\n\n## Connection URL\n\n```\nws(s)://<host>/timeseries/datapoints/subscription/listen/<id1>/<id2>/...\n```\n\nEach path segment after `.../listen/` is a subscription external id. The path may also be\nempty (`.../listen`) — connect with no subscriptions and add them with a `subscribe`\nmessage.\n\n## Authentication\n\nThe same Bearer JWT used for every other REST endpoint, verified during the HTTP handshake\nby the standard OAuth2 resource server filter. Missing or invalid tokens get a `401` and no\nupgrade occurs. A subscription that can't be resolved is reported as an error frame (see\nbelow) and skipped — it does not close the connection.\n\n## Subscription semantics\n\nThe per-subscription Pulsar subscription type is **Failover** (or **Key_Shared**): the\ndurable cursor lives in Pulsar, so a reconnect resumes from where it left off on whichever\ninstance the connection lands on. No load-balancer session affinity is required.\n\n## Server → client frames\n\nOne WS text frame per batch per subscription (up to 500 messages or every 500 ms,\nwhichever comes first), tagged with the owning subscription:\n\n```json\n{\n  \"subscriptionExternalId\": \"heater_2012_sub\",\n  \"messages\": [\n    {\n      \"messageId\": \"aGVhdGVyXzIwMTJfc3Vi.CAEQABgAIAAwAA\",\n      \"payload\": {\n        \"eventAction\": \"CREATE\",\n        \"eventObject\": \"DATAPOINTS\",\n        \"tenantId\": \"ebd85a20-...\",\n        \"items\": [\n          {\n            \"id\": 29,\n            \"externalId\": \"heater_2012_temp\",\n            \"valueType\": \"FLOAT\",\n            \"datapoints\": [\n              { \"timestamp\": \"2026-04-17T18:04:33Z\", \"value\": \"20.5\" }\n            ]\n          }\n        ]\n      }\n    }\n  ]\n}\n```\n\n`messageId` is opaque (it encodes the subscription so the ack is routed correctly). Use it\nto ack or nack later — do not parse it.\n\nAn unresolvable subscription produces an error frame instead:\n\n```json\n{ \"error\": true, \"subscriptionExternalId\": \"typo_sub\", \"reason\": \"not-found\" }\n```\n\n## Client → server frames\n\nChange which subscriptions are streamed:\n\n```json\n{ \"action\": \"subscribe\",   \"externalIds\": [\"turbine_3_sub\"] }\n{ \"action\": \"unsubscribe\", \"externalIds\": [\"heater_2012_sub\"] }\n{ \"action\": \"set\",         \"externalIds\": [\"a_sub\", \"b_sub\"] }\n```\n\nAck or nack delivered messages:\n\n```json\n{ \"action\": \"ack\",  \"messageIds\": [\"aGVhdGVyXzIwMTJfc3Vi.CAEQABgAIAAwAA\", \"...\"] }\n{ \"action\": \"nack\", \"messageIds\": [\"...\"] }\n```\n\n- `subscribe`/`add` — attach more subscriptions; `unsubscribe`/`remove` — detach;\n  `set` — replace the whole set.\n- `ack` — Pulsar forgets the message; `nack` — schedules redelivery.\n- Unknown `messageIds`/`externalIds` are silently ignored.\n- Messages left un-acked for Pulsar's `ackTimeout` are automatically redelivered.\n\n## Keepalive\n\nThe server sends a WS PING every 15 seconds. Any compliant client auto-replies with\nPONG. The container closes sessions idle for 45 seconds, so a dead client is detected\nwithin roughly that window.\n\n## Close codes you may observe\n\n| Code | Reason                                              |\n|------|-----------------------------------------------------|\n| 1000 | Normal closure (initiated by either side)           |\n| 1008 | Policy violation — missing tenant context           |\n| 1011 | Internal server error                               |\n",
        "operationId": "listenToSubscription",
        "parameters": [
          {
            "name": "externalIds",
            "in": "path",
            "description": "One or more subscription external ids as additional slash-separated path segments (e.g. `.../listen/sub_a/sub_b`). Each must match an existing subscription for the caller's tenant. May be omitted to connect with none and subscribe dynamically over the socket.",
            "required": false,
            "schema": {
              "type": "string",
              "example": "boiler_room_readings_sub/turbine_3_vibration_sub"
            }
          },
          {
            "name": "Authorization",
            "in": "header",
            "description": "Bearer JWT — same token used for all REST endpoints.",
            "required": true,
            "schema": {
              "type": "string",
              "example": "Bearer eyJhbGciOi..."
            }
          }
        ],
        "responses": {
          "101": {
            "description": "Switching Protocols — handshake accepted, the connection is now a WebSocket."
          },
          "401": {
            "description": "Missing or invalid Authorization header; no upgrade happens."
          }
        },
        "x-sort": "50"
      }
    },
    "/resources/delete": {
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Delete resources",
        "description": "Delete one or more resources. Identify each one by `id`, `externalId`, or\nboth. Unknown identifiers are silently skipped — the call succeeds as long\nas no safety check fails.\n\n### Connectivity must be preserved\nDeleting a resource removes **all** of its relationships (inbound and\noutbound) along with it. The delete is rejected with `400` if doing so would\nleave any surviving resource disconnected from a root resource — i.e. if it\nwould split off part of the graph. To remove such a node, include the nodes it\nwould strand in the same delete, or first re-attach them via another path. The\nresponse names the resources that would be stranded.\n\n### Idempotent\nCalling delete again for a resource that's already gone is a no-op and\nreturns `204`.\n\n### All-or-nothing\nA single safety-check failure rolls back the whole batch — nothing is\ndeleted unless everything can be.\n",
        "operationId": "delete",
        "requestBody": {
          "description": "Identifiers of the resources to delete. Each entry needs either `id` or `externalId`.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "externalId": "klp_valve_v9"
                  },
                  {
                    "id": 5677892
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The targeted resources (and any connected relationships pointing AT them) were deleted. No response body."
          },
          "400": {
            "description": "Something prevents the delete from being safe. Most commonly the delete would disconnect part of the graph from its root — the response names the resources that would be stranded so you can include them or re-attach them.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                },
                "example": {
                  "error": {
                    "code": 400,
                    "message": "Deleting this selection would disconnect resource(s) [42, 43] from the graph root. Include them in the deletion or keep a connecting path."
                  }
                }
              }
            }
          },
          "409": {
            "description": "Someone else changed or deleted one of the targeted resources while your delete was in flight. No resources were removed. Re-fetch state and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                },
                "example": {
                  "error": {
                    "code": 409,
                    "cause": "concurrency",
                    "message": "The resource was modified or removed by another request. Re-read and retry."
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Resources"
        ],
        "summary": "Delete resources",
        "description": "Delete one or more resources. Identify each one by `id`, `externalId`, or\nboth. Unknown identifiers are silently skipped — the call succeeds as long\nas no safety check fails.\n\n### Connectivity must be preserved\nDeleting a resource removes **all** of its relationships (inbound and\noutbound) along with it. The delete is rejected with `400` if doing so would\nleave any surviving resource disconnected from a root resource — i.e. if it\nwould split off part of the graph. To remove such a node, include the nodes it\nwould strand in the same delete, or first re-attach them via another path. The\nresponse names the resources that would be stranded.\n\n### Idempotent\nCalling delete again for a resource that's already gone is a no-op and\nreturns `204`.\n\n### All-or-nothing\nA single safety-check failure rolls back the whole batch — nothing is\ndeleted unless everything can be.\n",
        "operationId": "delete_1",
        "requestBody": {
          "description": "Identifiers of the resources to delete. Each entry needs either `id` or `externalId`.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "externalId": "klp_valve_v9"
                  },
                  {
                    "id": 5677892
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The targeted resources (and any connected relationships pointing AT them) were deleted. No response body."
          },
          "400": {
            "description": "Something prevents the delete from being safe. Most commonly the delete would disconnect part of the graph from its root — the response names the resources that would be stranded so you can include them or re-attach them.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                },
                "example": {
                  "error": {
                    "code": 400,
                    "message": "Deleting this selection would disconnect resource(s) [42, 43] from the graph root. Include them in the deletion or keep a connecting path."
                  }
                }
              }
            }
          },
          "409": {
            "description": "Someone else changed or deleted one of the targeted resources while your delete was in flight. No resources were removed. Re-fetch state and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                },
                "example": {
                  "error": {
                    "code": 409,
                    "cause": "concurrency",
                    "message": "The resource was modified or removed by another request. Re-read and retry."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/policies/delete": {
      "post": {
        "tags": [
          "Policies"
        ],
        "summary": "Delete policy nodes",
        "description": "Delete one or more policy nodes.",
        "operationId": "deletePolicies",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The policy nodes were deleted. No response body."
          },
          "200": {
            "description": "The policy after the update.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Policy Collection"
                }
              }
            }
          },
          "409": {
            "description": "Concurrency conflict — another request modified or deleted the policy between read and write. Clients should re-fetch the current state and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Policies"
        ],
        "summary": "Delete policy nodes",
        "description": "Delete one or more policy nodes.",
        "operationId": "deletePolicies_1",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The policy nodes were deleted. No response body."
          },
          "200": {
            "description": "The policy after the update.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Policy Collection"
                }
              }
            }
          },
          "409": {
            "description": "Concurrency conflict — another request modified or deleted the policy between read and write. Clients should re-fetch the current state and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      }
    },
    "/labels/delete": {
      "post": {
        "tags": [
          "Labels"
        ],
        "summary": "Delete labels",
        "description": "Delete one or more labels by `id`. The delete is rejected if any resource\nis still using the label — remove the label from those resources first\n(via `POST /resources/update` with `labels.remove`).\n",
        "operationId": "delete_2",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "A collection with updated label objects is returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label Collection"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Labels"
        ],
        "summary": "Delete labels",
        "description": "Delete one or more labels by `id`. The delete is rejected if any resource\nis still using the label — remove the label from those resources first\n(via `POST /resources/update` with `labels.remove`).\n",
        "operationId": "delete_3",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "A collection with updated label objects is returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label Collection"
                }
              }
            }
          }
        }
      }
    },
    "/events/delete": {
      "post": {
        "tags": [
          "Events"
        ],
        "summary": "Delete events",
        "description": "Delete one or more events by UUID `id` or `externalId`.\n\n### Cannot be undone\nOnce deleted, an event is gone from queries immediately. Historical\nreports referencing the event by id will no longer resolve.\n\n### Idempotent\nDeleting an event that's already gone returns `200` and is a no-op.\n",
        "operationId": "delete_4",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UUIDAndExternalIdCollectionDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "externalId": "alarm_pipe_overpressure_2026_04_22_14_30"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The events were deleted. No response body."
          },
          "400": {
            "description": "Malformed request — e.g. neither `id` nor `externalId` supplied on an entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Events"
        ],
        "summary": "Delete events",
        "description": "Delete one or more events by UUID `id` or `externalId`.\n\n### Cannot be undone\nOnce deleted, an event is gone from queries immediately. Historical\nreports referencing the event by id will no longer resolve.\n\n### Idempotent\nDeleting an event that's already gone returns `200` and is a no-op.\n",
        "operationId": "delete_5",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UUIDAndExternalIdCollectionDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "externalId": "alarm_pipe_overpressure_2026_04_22_14_30"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The events were deleted. No response body."
          },
          "400": {
            "description": "Malformed request — e.g. neither `id` nor `externalId` supplied on an entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          }
        }
      }
    },
    "/edges/delete": {
      "post": {
        "tags": [
          "Relationships"
        ],
        "summary": "Delete relationships",
        "description": "Delete one or more relationships by `id`. Deletes the link only — the\nresources at each end stay intact.\n\n### Idempotent\nUnknown ids are silently skipped.\n",
        "operationId": "delete_6",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The relationships were deleted. No response body."
          },
          "409": {
            "description": "Concurrency conflict — another request modified or deleted the relationship between read and write. Clients should re-fetch the current state and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Relationships"
        ],
        "summary": "Delete relationships",
        "description": "Delete one or more relationships by `id`. Deletes the link only — the\nresources at each end stay intact.\n\n### Idempotent\nUnknown ids are silently skipped.\n",
        "operationId": "delete_7",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The relationships were deleted. No response body."
          },
          "409": {
            "description": "Concurrency conflict — another request modified or deleted the relationship between read and write. Clients should re-fetch the current state and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      }
    },
    "/datasets/delete": {
      "post": {
        "tags": [
          "Data sets"
        ],
        "summary": "Delete datasets",
        "description": "Delete one or more datasets by `id` or `externalId`.\n\nDeleting a dataset does **not** delete the resources or timeseries that\nreference it — they continue to exist with their `dataSetId` cleared.\nRemove those separately via `POST /resources/delete` or\n`POST /timeseries/delete` if you want them gone too.\n",
        "operationId": "delete_8",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "externalId": "sap_work_orders"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The datasets were deleted. No response body."
          },
          "409": {
            "description": "Someone else changed or deleted one of the datasets while your delete was in flight. No datasets were removed. Re-fetch state and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Data sets"
        ],
        "summary": "Delete datasets",
        "description": "Delete one or more datasets by `id` or `externalId`.\n\nDeleting a dataset does **not** delete the resources or timeseries that\nreference it — they continue to exist with their `dataSetId` cleared.\nRemove those separately via `POST /resources/delete` or\n`POST /timeseries/delete` if you want them gone too.\n",
        "operationId": "delete_9",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "externalId": "sap_work_orders"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The datasets were deleted. No response body."
          },
          "409": {
            "description": "Someone else changed or deleted one of the datasets while your delete was in flight. No datasets were removed. Re-fetch state and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      }
    },
    "/subscriptions/delete": {
      "post": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Delete subscription",
        "description": "Delete one or more subscriptions by `id` or `externalId`. Any connected\nWebSocket clients are disconnected and will not reconnect.\n\n### Reject if clients are connected\nThe delete is rejected with `400` if at least one client is actively\nconnected to the subscription — to avoid silently dropping live feeds.\nDisconnect the clients (or wait for them to drop) and retry.\n\n### Idempotent\nEntries that don't exist are silently skipped; the call returns `204` as\nlong as no active client prevents a delete.\n",
        "operationId": "deleteSubscription",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "externalId": "fleet_dashboard"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The subscriptions were deleted. No response body."
          },
          "400": {
            "description": "At least one subscription still has a live client connected. The response names the offending `externalId` and the connected-consumer count.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                },
                "example": {
                  "error": {
                    "code": 400,
                    "message": "Cannot delete subscription while clients are connected.",
                    "fields": [
                      {
                        "externalId": "fleet_dashboard",
                        "connectedConsumers": "2"
                      }
                    ]
                  }
                }
              }
            }
          },
          "409": {
            "description": "Someone else changed or deleted one of the subscriptions while your delete was in flight. No subscriptions were removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Delete subscription",
        "description": "Delete one or more subscriptions by `id` or `externalId`. Any connected\nWebSocket clients are disconnected and will not reconnect.\n\n### Reject if clients are connected\nThe delete is rejected with `400` if at least one client is actively\nconnected to the subscription — to avoid silently dropping live feeds.\nDisconnect the clients (or wait for them to drop) and retry.\n\n### Idempotent\nEntries that don't exist are silently skipped; the call returns `204` as\nlong as no active client prevents a delete.\n",
        "operationId": "deleteSubscription_1",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "externalId": "fleet_dashboard"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "The subscriptions were deleted. No response body."
          },
          "400": {
            "description": "At least one subscription still has a live client connected. The response names the offending `externalId` and the connected-consumer count.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                },
                "example": {
                  "error": {
                    "code": 400,
                    "message": "Cannot delete subscription while clients are connected.",
                    "fields": [
                      {
                        "externalId": "fleet_dashboard",
                        "connectedConsumers": "2"
                      }
                    ]
                  }
                }
              }
            }
          },
          "409": {
            "description": "Someone else changed or deleted one of the subscriptions while your delete was in flight. No subscriptions were removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      }
    },
    "/functions/delete": {
      "post": {
        "tags": [
          "Functions"
        ],
        "summary": "Delete function",
        "description": "Delete one or more functions by id or externalId. Deleting a function removes all of its relationships; the delete is rejected if it would strand a surviving node.",
        "operationId": "deleteFunction",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "Function(s) deleted. No response body."
          },
          "400": {
            "description": "Bad request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Functions"
        ],
        "summary": "Delete function",
        "description": "Delete one or more functions by id or externalId. Deleting a function removes all of its relationships; the delete is rejected if it would strand a surviving node.",
        "operationId": "deleteFunction_1",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "Function(s) deleted. No response body."
          },
          "400": {
            "description": "Bad request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          }
        }
      }
    },
    "/files": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Get a file or folder",
        "description": "Return a single file or folder by numeric id or by externalId (supply one). Visible only if the caller can read its dataset; dataset-less nodes are public.",
        "operationId": "getByIdOrExternalId",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "description": "Numeric id of the file or folder. Supply this or externalId.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5677892
          },
          {
            "name": "externalId",
            "in": "query",
            "description": "External id of the file or folder. Supply this or id.",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "reports/2026/q1.pdf"
          }
        ],
        "responses": {
          "200": {
            "description": "The file or folder.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/File Collection"
                }
              }
            }
          },
          "400": {
            "description": "Neither id nor externalId supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "A file id or externalId is required."
                }
              }
            }
          },
          "404": {
            "description": "Not found or not readable.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "File or folder not found."
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Files"
        ],
        "summary": "Upload file",
        "description": "Upload a file with an HTTP PUT to /files. The file content is the raw request body; all metadata travels in request headers, which means the server validates and authorises the upload before it reads a single body byte. Headers: 'X-Datahub-Path' (required, the full destination path including the filename, each path segment percent-encoded), 'X-Datahub-External-Id' (optional, percent-encoded; defaults to the filename and is always sanitised to a lowercase slug), 'X-Datahub-Dataset-Id' (optional, dataset id for access control), and the optional, percent-encoded 'X-Datahub-Description', 'X-Datahub-Source', 'X-Datahub-Source-Date-Created', 'X-Datahub-Source-Last-Updated' (the two source dates are ISO-8601 with a zone or offset, e.g. 2026-06-24T12:00:00Z, or epoch millis as a fallback), 'X-Datahub-Metadata' (a JSON object) and 'X-Datahub-Related-Resources' (a JSON array of resource ids). 'Content-Type' is the file's MIME type; omit it or send 'application/octet-stream' to have the server auto-detect it.",
        "operationId": "upload",
        "requestBody": {
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The uploaded file.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/File Collection"
                }
              }
            }
          },
          "400": {
            "description": "Invalid upload request.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "X-Datahub-Path must include a filename"
                }
              }
            }
          },
          "409": {
            "description": "Upload failed, a file already exists at that path.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "A file already exists at the target path."
                }
              }
            }
          }
        }
      }
    },
    "/units/byids": {
      "post": {
        "tags": [
          "Units"
        ],
        "summary": "Find units by id or externalId",
        "description": "Look up several units in one call. Each entry in `items[]` needs either `id` or `externalId`. Missing ones are silently omitted.",
        "operationId": "byids",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Returns a list of units. The list is empty if not unit is found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Unit"
                }
              }
            }
          }
        }
      }
    },
    "/subscriptions/list": {
      "post": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "List subscriptions",
        "description": "List subscriptions in your tenant.\n\nLeave the body empty (or omit `filter`) to list every subscription. Provide\na `filter.timeseries[]` list to return only subscriptions that include at\nleast one of the named timeseries.\n\n`limit` caps the result size (default 100, max 10 000). `sort` controls the\norder; default is `dateCreated` descending (newest first).\n",
        "operationId": "listSubscriptions",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscriptionRetriever"
              },
              "examples": {
                "List all (default)": {
                  "description": "List all (default)",
                  "value": {}
                },
                "Only subscriptions touching a specific timeseries": {
                  "description": "Only subscriptions touching a specific timeseries",
                  "value": {
                    "limit": 100,
                    "filter": {
                      "timeseries": [
                        {
                          "externalId": "sensor_temp_room_a"
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriptions matching the filter, ordered per `sort` (default newest first).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscription Collection"
                }
              }
            }
          }
        }
      }
    },
    "/subscriptions/create": {
      "post": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Create subscription",
        "description": "Create one or more **subscriptions**. A subscription is a named stream that\ndelivers live data-points from one or more timeseries to a client (e.g. a\ndashboard) over a WebSocket connection.\n\nEach subscription needs a unique `externalId` within your tenant, a `name`,\nand a non-empty `timeseries[]` list identifying which timeseries to stream\n— by `id`, `externalId`, or both.\n\n### Retention\nThe subscription's backlog survives across client disconnects: if a client\ndrops and reconnects, it resumes from where it left off. To reset the\nposition, delete and recreate the subscription.\n",
        "operationId": "createSubscription",
        "requestBody": {
          "description": "Subscriptions to create.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Subscription Collection"
              },
              "examples": {
                "Dashboard feed for two sensors": {
                  "description": "Dashboard feed for two sensors",
                  "value": {
                    "items": [
                      {
                        "externalId": "fleet_dashboard",
                        "name": "Fleet dashboard live feed",
                        "timeseries": [
                          {
                            "externalId": "sensor_temp_room_a"
                          },
                          {
                            "externalId": "sensor_flow_main"
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "The subscription was created and is ready to accept WebSocket clients.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscription Collection"
                }
              }
            }
          },
          "400": {
            "description": "Typical causes: `externalId` already in use, `timeseries[]` empty, or one of the referenced timeseries doesn't exist. The `fields` list tells you which input was wrong.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          },
          "409": {
            "description": "One of the referenced timeseries was modified or deleted while your subscription was being created. Re-fetch the timeseries and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      }
    },
    "/resources/update": {
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Update resources and/or relationships",
        "description": "Change fields on existing resources or relationships. Identify each entry\nby either `id` or `externalId` — whichever you have.\n\nOnly the fields you include in the `update` block are changed. Fields you\nleave out keep their current value. Every updatable field is a small object\nwith these options:\n\n- `\"set\"` — replace the field with this value.\n- `\"setNull\": true` — clear the field. Rejected with a 400 on `name` and\n  `externalId`, which every resource must have; use `set` to change them.\n- `\"add\"` / `\"remove\"` — for collection fields (`metadata`, `labels`), add\n  or remove entries while leaving the rest untouched.\n\n### All-or-nothing\nIf any entry fails validation, nothing is saved. Fix the offending entry and\nresend.\n",
        "operationId": "update_1",
        "requestBody": {
          "description": "Resources and/or relationships to update. Only fields you name in `update` are changed.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Graph Data Nodes and Edges"
              },
              "examples": {
                "Rename and add metadata": {
                  "description": "Rename and add metadata",
                  "value": {
                    "nodes": [
                      {
                        "externalId": "klp_pipe_ws_a1212_dl",
                        "update": {
                          "name": {
                            "set": "klp pipe ws-a1212-dl (renamed)"
                          },
                          "description": {
                            "set": "Water stream pipe — primary loop"
                          },
                          "metadata": {
                            "add": {
                              "inspected_by": "olav"
                            }
                          },
                          "labels": {
                            "add": [
                              "CRITICAL"
                            ]
                          }
                        }
                      }
                    ],
                    "relations": []
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The resources and relationships after the update, with current values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GraphResources"
                },
                "example": {
                  "nodes": [
                    {
                      "id": 5677892,
                      "externalId": "klp_pipe_ws_a1212_dl",
                      "name": "klp pipe ws-a1212-dl (renamed)",
                      "description": "Water stream pipe — primary loop",
                      "labels": [
                        "PIPE",
                        "CRITICAL"
                      ],
                      "metadata": {
                        "work_order": "wo-sap-12344",
                        "inspected_by": "olav"
                      }
                    }
                  ],
                  "relations": []
                }
              }
            }
          },
          "400": {
            "description": "The request has a problem the server could spot before saving anything. Typical causes: neither `id` nor `externalId` supplied, the targeted resource doesn't exist, or an update rule is malformed (`set` and `setNull` both present on the same field). The `fields` list tells you which input was wrong.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                },
                "example": {
                  "error": {
                    "code": 400,
                    "message": "Resource cannot be found.",
                    "fields": [
                      {
                        "externalId": "klp_pipe_ws_a1212_dl",
                        "id": "null"
                      }
                    ]
                  }
                }
              }
            }
          },
          "409": {
            "description": "Someone else changed or deleted the resource while your update was in flight. Your write was not applied. Re-fetch the resource with `POST /resources/byids` and retry the update with fresh state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                },
                "example": {
                  "error": {
                    "code": 409,
                    "cause": "concurrency",
                    "message": "The resource was modified or removed by another request. Re-read and retry."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests — back off and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataWrapper"
                }
              }
            }
          }
        }
      }
    },
    "/resources/search": {
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Full-text search resources",
        "description": "Free-text search across **every node type** — assets, timeseries, functions,\nresources, data sets and policies — the same breadth as\n`POST /resources/filter`. The phrase is matched against `name`, `externalId`\nand `description`. Matching is fuzzy and word-aware: search for `pipe` and\nyou'll also find `pipes`, `piping`, and multi-word names containing the term.\n\n### Narrowing the results\n`filter` is optional and takes the same criteria as `POST /resources/filter`.\nIt only ever *removes* matches — the phrase decides what the candidates are.\nUse it to say \"pipes, but only in this data set\" or \"pipes, but only\ntimeseries\". Omit it for no narrowing.\n\nIf you don't need a phrase at all, use `POST /resources/filter`: a structured\nquery on its own is faster and more predictable than one bolted to a search.\n\n`limit` caps the result size (default 100, max 1000).\n\n### Result order\nRanked by relevance (`ts_rank`), strongest match first, with `id` as a\ntie-break so equal-scoring rows keep a stable order and repeated identical\nrequests agree. Ranking means the database scores and sorts every match before\napplying `limit`, so a very broad phrase costs more than a narrow one.\n",
        "operationId": "get",
        "requestBody": {
          "description": "Search phrase, optional filter, optional limit.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchBodyResource Query Filter"
              },
              "example": {
                "search": {
                  "query": "pipe"
                },
                "filter": {
                  "nodeType": [
                    "resource"
                  ],
                  "labels": [
                    "Asset"
                  ]
                },
                "limit": 50
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Resources ranked by how well they match the search phrase.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Resource Collection"
                }
              }
            }
          },
          "400": {
            "description": "The request failed validation — usually a missing or too-short `query`. Response lists the offending fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          }
        }
      }
    },
    "/resources/filter": {
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "List resources matching filter criteria",
        "description": "Return the resources that match a set of filters. All filters are combined\nwith AND — a resource must match every filter you supply to be included.\n\nSupported filters:\n- `name` — case-insensitive substring match. Accepts `%` as a wildcard.\n- `id` — exact numeric id.\n- `externalId` — exact externalId.\n- `isRoot` — `true` or `false`.\n- `dataSetId` — only resources belonging to any of these datasets.\n- `metadata` — every key/value in this object must be present on the resource.\n- `source` — case-insensitive substring match on `source`.\n- `createdTime.min` / `createdTime.max` — ISO-8601 timestamp bounds (inclusive).\n- `lastUpdatedTime.min` / `lastUpdatedTime.max` — same for last-updated.\n\n### This is the generic node query\nUnlike `/datasets/filter`, `/timeseries/filter` and `/events/filter`, which each\nanswer for one type, this endpoint spans **every node type** — assets,\ntimeseries, functions, resources, data sets and policies share one table and one\nset of criteria. Narrow it with `nodeType` (`[\"resource\", \"timeseries\"]`) when\nyou want only some; omit it for all. Every node carries its type as a label, so\nyou can tell what came back.\n\nEvery list field also accepts a bare value, so `\"name\": \"pipe*\"` and\n`\"name\": [\"pipe*\"]` mean the same thing. `externalId`, `name` and `source`\nare pattern lists: `*` and `%` are wildcards, `_` is literal, matching is\ncase-insensitive, and an entry without a wildcard matches exactly. `labels`\nmust **all** be present, and a null `metadata` value matches the key alone.\n\n### Ordering and paging\nResults come newest created first, capped by `limit` (default 1000, max 10000).\n`sort` takes one property — `id`, `externalId`, `name`, `source`,\n`description`, `createdTime`, `lastUpdatedTime` or `dataSetId` — with `order` of\n`asc` or `desc`; `id` is always appended so the order is total and a page\nboundary can never fall inside a run of equal values. Nulls sort last ascending\nand first descending.\n\nThe response carries `nextCursor` when there may be more. Send it back as\n`cursor` for the following page and keep going while it is present. This is\nkeyset paging, not `OFFSET`: each page is a range seeked to rather than rows\ncounted and thrown away, so a deep page costs what a shallow one does, and rows\nwritten elsewhere cannot shift the walk into repeating or skipping one. Send the\ncursor back with **the same `sort` it came from** — a cursor is a position in\none particular order, so continuing it under another is rejected with `400`\nrather than answered with a page that is quietly wrong.\n\nA cursor that cannot be read — truncated, edited, or from an older format — is\nrejected with `400` too, rather than quietly returning the first page: a client\nthat pages by echoing back what it was given would otherwise loop on page one\nforever, never advancing and never told anything was wrong.\n",
        "operationId": "filter_1",
        "requestBody": {
          "description": "Filter criteria and optional limit.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Resource Query"
              },
              "example": {
                "limit": 100,
                "filter": {
                  "name": [
                    "pipe*"
                  ],
                  "externalId": [
                    "klp_pipe_*"
                  ],
                  "labels": [
                    "PIPE"
                  ],
                  "dataSetId": [
                    {
                      "id": "12"
                    }
                  ],
                  "metadata": {
                    "work_order": "wo-sap-12344"
                  },
                  "createdTime": {
                    "min": "2026-01-01T00:00:00Z"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The resources that match every supplied filter. Empty `items[]` means nothing matched.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Resource Collection"
                }
              }
            }
          }
        }
      }
    },
    "/resources/fetch-related": {
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Fetch resources connected to a starting resource",
        "description": "Starting from one resource, walk outward along its relationships and return\neverything reachable within a given `depth`.\n\nUseful for \"what is connected to this asset?\" style questions — for example,\n*show me every pipe, valve, and sensor attached to this processing unit*.\n\nIdentify the starting resource with either its numeric `id` or its\n`externalId`. `depth` controls how many relationship hops to follow; keep it\nsmall (1–3) unless you know the graph is sparse, because the result set grows\nquickly.\n",
        "operationId": "fetchRelatedResources",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RelatedResourcesForm"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Returns the starting resource plus every resource and relationship reached within `depth` hops.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResourceNetwork"
                }
              }
            }
          },
          "404": {
            "description": "The starting resource was not found. Check `id` / `externalId` and your tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "Could not find resource with id: 42"
                }
              }
            }
          }
        }
      }
    },
    "/resources/fetch-nearest": {
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Find the nearest resources of a given label",
        "description": "Breadth-first from a starting resource (numeric `id`), return the closest `limit`\nnodes carrying one of `endLabels` (e.g. `[\"TIMESERIES\"]`) plus the sub-graph that\nconnects them. The cap is on matching END-nodes, not on hop depth or total node\ncount — so \"the 10 nearest time series\" is exact however many intermediate nodes\nlie between them. `excludedLabels` (e.g. `[\"POLICY\"]`) are never traversed or\nreturned; `relationshipTypes` restricts which edges may be followed.\n",
        "operationId": "fetchNearestResources",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FetchNearestResourcesForm"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The nearest matching nodes plus every node and relationship on the paths to them.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResourceNetwork"
                }
              }
            }
          },
          "404": {
            "description": "The starting resource was not found. Check `id` and your tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "Could not find resource with id: 42"
                }
              }
            }
          }
        }
      }
    },
    "/resources/create": {
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Create resources and/or relationships",
        "description": "Create one or more **resources** and the **relationships** that connect them,\nin a single request.\n\nA resource is any object you want to track: a physical asset, a document,\na measurement point, and so on. A relationship is a directed link from one\nresource to another (for example *pipe flows to valve*).\n\n### What you send\n- `nodes[]` — the resources to create. Each resource must have a unique\n  `externalId` within your tenant, a `name`, and at least one `labels` entry.\n- `relations[]` — optional links between resources. You can reference resources\n  being created in the same request by their `externalId`, or link to resources\n  that already exist.\n\n### What you get back\nThe same resources and relations, now with server-assigned numeric `id`s\nyou can use in later calls.\n\n### All-or-nothing\nIf any one resource or relation in the request fails validation, none of them\nare created. Fix the offending entry and resend.\n",
        "operationId": "create_1",
        "requestBody": {
          "description": "The resources and relations to create.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateResources"
              },
              "examples": {
                "Pipe connected to a valve": {
                  "description": "Pipe connected to a valve",
                  "value": {
                    "nodes": [
                      {
                        "externalId": "klp_pipe_ws_a1212_dl",
                        "name": "klp pipe ws-a1212-dl",
                        "description": "Water stream pipe",
                        "labels": [
                          "PIPE"
                        ],
                        "dataSetId": 12,
                        "source": "dolphin_rex_pipes",
                        "metadata": {
                          "work_order": "wo-sap-12344"
                        }
                      },
                      {
                        "externalId": "klp_valve_v9",
                        "name": "KLP valve V9",
                        "labels": [
                          "VALVE"
                        ]
                      }
                    ],
                    "relations": [
                      {
                        "fromExternalId": "klp_pipe_ws_a1212_dl",
                        "toExternalId": "klp_valve_v9",
                        "relationshipType": "FLOWS_TO"
                      }
                    ]
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Resources and relationships were created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GraphResources"
                },
                "examples": {
                  "Two resources and one relationship": {
                    "description": "Two resources and one relationship",
                    "value": {
                      "nodes": [
                        {
                          "id": 5677892,
                          "externalId": "klp_pipe_ws_a1212_dl",
                          "name": "klp pipe ws-a1212-dl",
                          "description": "Water stream pipe",
                          "labels": [
                            "PIPE"
                          ],
                          "dataSetId": 12,
                          "source": "dolphin_rex_pipes",
                          "metadata": {
                            "work_order": "wo-sap-12344"
                          }
                        },
                        {
                          "id": 5677893,
                          "externalId": "klp_valve_v9",
                          "name": "KLP valve V9",
                          "labels": [
                            "VALVE"
                          ]
                        }
                      ],
                      "relations": [
                        {
                          "id": 341,
                          "start": 5677892,
                          "end": 5677893,
                          "type": "FLOWS_TO"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request has a problem the server could spot before saving anything. Typical causes: missing required field, `externalId` too short or with forbidden characters, referenced `dataSetId` doesn't exist, or a relation points at a resource that isn't in the request and doesn't exist. The `fields` list tells you which input was wrong.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                },
                "example": {
                  "error": {
                    "code": 400,
                    "message": "Could not find fromNode",
                    "fields": [
                      {
                        "externalId": "klp_valve_v9",
                        "id": "null"
                      }
                    ]
                  }
                }
              }
            }
          },
          "409": {
            "description": "A resource with one of the `externalId`s you sent already exists in your tenant. The `duplicated` list tells you which ones. Either pick a different `externalId`, or use `POST /resources/update` to modify the existing resource.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DuplicateError"
                },
                "example": {
                  "error": {
                    "code": 409,
                    "message": "External id already exists.",
                    "duplicated": [
                      {
                        "externalId": "klp_pipe_ws_a1212_dl"
                      }
                    ]
                  }
                }
              }
            }
          },
          "422": {
            "description": "One or more fields failed validation rules (length limits, character set, required-ness). Response lists the offending fields per entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataWrapper"
                }
              }
            }
          }
        }
      }
    },
    "/resources/byids": {
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Find multiple resources by id or externalId",
        "description": "Look up several resources in one call. Each entry in `items[]` carries\neither a numeric `id`, an `externalId`, or both — mix and match freely.\n\nResources that don't exist are simply omitted from the response; the call\ndoesn't fail. Compare the returned `items[]` against what you asked for to\ndetect missing ones.\n",
        "operationId": "findByIdList_1",
        "requestBody": {
          "description": "Identifiers of the resources to look up. Each entry needs either `id` or `externalId`.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "id": 5677892
                  },
                  {
                    "externalId": "klp_valve_v9"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The resources that were found. Missing ones are silently left out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Resource Collection"
                }
              }
            }
          }
        }
      }
    },
    "/policies/update": {
      "post": {
        "tags": [
          "Policies"
        ],
        "summary": "Update a policy",
        "description": "Change fields on existing policies. Send one `items[]` entry per policy,\nidentified by `id` or `externalId`, plus an `update` block naming only the\nfields to change. Every entry is applied; the response returns the updated\npolicies in the same order.\n\nThe entry's `externalId` says *which* policy to change; to change the\nexternal id itself, set `update.externalId`.\n\nEach updatable field is an object, the same rules as `POST /resources/update`:\n- `\"set\"` — replace the field with this value.\n- `\"setNull\": true` — clear the field (`description` and `source` only).\n  Ignored when `set` is also present: a value you supplied is never discarded.\n- `\"add\"` / `\"remove\"` — for `metadata`, add or remove specific keys.\n\nA field you leave out is left alone. That matters most for `deactivated`:\nomitting it keeps a switched-off policy switched off, where the previous\nwhole-object form silently re-activated it on any unrelated edit.\n",
        "operationId": "updatePolicy",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Update Policy Collection"
              }
            }
          },
          "required": true
        },
        "responses": {
          "400": {
            "description": "The request carried no policies, or one failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          },
          "409": {
            "description": "Concurrency conflict — another request modified or deleted the policy between read and write. Clients should re-fetch the current state and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictError"
                }
              }
            }
          }
        }
      }
    },
    "/policies/naming/check": {
      "post": {
        "tags": [
          "Policies"
        ],
        "summary": "Check external ids against the naming policy",
        "description": "Preflight. Runs the naming policy over candidate external ids and reports what it\nwould do, without writing anything.\n\nTwo uses: validating as you type, and answering \"what would this policy do to my\nexisting ids\" before enabling it — which is the difference between a policy\npeople trust and one they switch off.\n\nUses the same evaluator as the write path, so the answer cannot disagree with\nwhat a real write would do. Only non-conforming ids are returned; an empty\n`findings` array means every id is fine.\n\nThis reports what *would* happen. Violations that were allowed through and\nrecorded are a different thing and are not returned here: they are events, so\nthey are read with `POST /events/filter` on `type = \"policy_finding\"`.\n",
        "operationId": "check",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NamingCheckForm"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "What the policy would decide for each id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/PolicyFinding"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "No read access to the requested data set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          }
        }
      }
    },
    "/policies/create": {
      "post": {
        "tags": [
          "Policies"
        ],
        "summary": "Create policy",
        "description": "Create one or more **policy** records from a policy template. A policy\nattaches to a dataset (via `POST /datasets/update`) and enforces rules\nlike \"this dataset is read-only\" or \"mask the `salary` field\".\n\nEach entry needs:\n- `name` — a unique name for the policy.\n- `templateId` — which policy type to instantiate. List the available\n  types with `GET /policies/types`.\n- `externalId` — optional, for integrations that need a stable identifier.\n",
        "operationId": "create_2",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Policy Collection"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Returns the created Policy node(s).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Policy Collection"
                }
              }
            }
          }
        }
      }
    },
    "/labels/update": {
      "post": {
        "tags": [
          "Labels"
        ],
        "summary": "Update labels",
        "description": "Change `description`, `color`, or `i18nCode` on existing labels. Identify each by `id` or by `name` (most callers use the name; the id is synthetic). To rename a label, identify it by `id`, since a name used to look it up can't also be the new name.",
        "operationId": "update_2",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Label Collection"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "A collection with updated label objects is returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label Collection"
                }
              }
            }
          }
        }
      }
    },
    "/labels/create": {
      "post": {
        "tags": [
          "Labels"
        ],
        "summary": "Create labels",
        "description": "Create one or more labels up-front. Labels are usually auto-created the\nfirst time they're referenced in `POST /resources/create`; this endpoint\nis for admin flows that want to pre-seed label names, colors, or i18n\ncodes before they're used.\n\nEach label needs a unique `name` within your tenant. Optional fields:\n`description`, `color` (hex e.g. `#3A9F2E`), `i18nCode` for localized UI.\n",
        "operationId": "create_3",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Label Collection"
              }
            }
          },
          "required": true
        },
        "responses": {
          "409": {
            "description": "A label with this name already exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DuplicateError"
                }
              }
            }
          },
          "200": {
            "description": "A collection with newly created label objects is returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label Collection"
                }
              }
            }
          }
        }
      }
    },
    "/functions/update": {
      "post": {
        "tags": [
          "Functions"
        ],
        "summary": "Update function",
        "description": "Update one or more functions (and any relations). Only the fields named in each entry's `update` block are changed.",
        "operationId": "updateFunction",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Graph Data Nodes and Edges"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Function(s) updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Bad request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          }
        }
      }
    },
    "/functions/create": {
      "post": {
        "tags": [
          "Functions"
        ],
        "summary": "Create function",
        "description": "Create one or more functions. A function is a plain datastore node with the same shape as a resource; each needs a unique externalId and a name.",
        "operationId": "createFunction",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Function Collection"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Function(s) created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Function Collection"
                }
              }
            }
          },
          "400": {
            "description": "Bad request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          }
        }
      }
    },
    "/files/update": {
      "post": {
        "tags": [
          "Files"
        ],
        "summary": "Update a file or folder",
        "description": "Rename, move, reassign the dataset, or edit the description, source, metadata or related resources of a file or folder. Identify the node by externalId or id; every other field is optional and applied only when present. Moving creates the destination folder if it is missing. Assigning a dataset to a folder also fills it in on every descendant that currently has no dataset (already-governed subtrees are left untouched).",
        "operationId": "update_3",
        "responses": {
          "200": {
            "description": "The updated file or folder.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/File Collection"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (e.g. illegal name).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "Invalid folder path"
                }
              }
            }
          },
          "403": {
            "description": "No write permission.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "No write permission on this dataset."
                }
              }
            }
          },
          "404": {
            "description": "File or folder not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "File or folder not found."
                }
              }
            }
          },
          "409": {
            "description": "A file or folder already exists at the target path.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "A file already exists at the target path."
                }
              }
            }
          }
        }
      }
    },
    "/files/restore": {
      "post": {
        "tags": [
          "Files"
        ],
        "summary": "Restore deleted files",
        "description": "Move soft-deleted files out of the trash back to their original location and clear the deleted flag. Identify each by id or (trashed) externalId. Files only. Never overwrites: if a file's original name/path or externalId is already taken, or its original folder is gone, the request is refused (409) and nothing is restored.",
        "operationId": "restore",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The restored files.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/File Collection"
                }
              }
            }
          },
          "403": {
            "description": "No write permission on a file's dataset.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "No write permission on this dataset."
                }
              }
            }
          },
          "404": {
            "description": "None of the given ids/externalIds match a deleted file.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "File or folder not found."
                }
              }
            }
          },
          "409": {
            "description": "Original name/path or externalId already taken, or the original folder is gone.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "A file already exists at the original path."
                }
              }
            }
          }
        }
      }
    },
    "/files/delete": {
      "post": {
        "tags": [
          "Files"
        ],
        "summary": "Delete files or folders",
        "description": "Delete file or folder by id or external id.",
        "operationId": "delete_12",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "File or folder was deleted. No response body."
          },
          "409": {
            "description": "The folder is not empty.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "Folder is not empty."
                }
              }
            }
          }
        }
      }
    },
    "/events/update": {
      "post": {
        "tags": [
          "Events"
        ],
        "summary": "Update events",
        "description": "Change fields on existing events. Identify each one by UUID `id` or\n`externalId`. Only fields you name in the `update` block are changed.\n\nUses the standard `set` / `setNull` / `add` / `remove` rules — see\n`POST /resources/update` for details. An event's required fields —\n`externalId`, `type` and `eventTime` — reject `setNull` with a 400; `dataSetId`\naccepts it and detaches the event from its dataset.\n\n### Use sparingly\nEvent updates run a replace-and-cleanup on the stored record. While the\nupdate is in flight, a read against the same event can briefly return the\npre-update version or a duplicate. Prefer creating a follow-up event that\ncorrects the record rather than mutating the original when it matters for\naudit.\n",
        "operationId": "update_4",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Update Event Collection"
              },
              "example": {
                "items": [
                  {
                    "externalId": "alarm_pipe_overpressure_2026_04_22_14_30",
                    "update": {
                      "status": {
                        "set": "acknowledged"
                      },
                      "metadata": {
                        "add": {
                          "acked_by": "olav"
                        }
                      }
                    }
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The events after the update, with current values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Event Collection"
                }
              }
            }
          },
          "400": {
            "description": "The request has a problem the server spotted before saving. Typical causes: neither `id` nor `externalId` supplied, the event doesn't exist, or `set` and `setNull` both present on the same field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          },
          "409": {
            "description": "The new `externalId` already belongs to another event. Pick a different one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DuplicateError"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests — back off and retry."
          }
        }
      }
    },
    "/events/search": {
      "post": {
        "tags": [
          "Events"
        ],
        "summary": "Full-text search events",
        "description": "Case-insensitive **substring** search across events: the phrase is matched\nagainst `externalId`, `description` and the event's metadata *values*.\n\nUnlike the resource, data set and timeseries searches this one is not\nword-aware and does not rank — searching `pump` finds `pump` and `pumps` but\nnot `pumping`, and results come back newest first by `eventTime`. Events live\nin ClickHouse rather than the node table and have no full-text index.\n\n### Narrowing the results\n`filter` is optional and takes the same criteria as `POST /events/filter` —\n`type`, `status`, `eventTime` ranges, `relatedResources`, and the rest. It\nonly ever *removes* matches; the phrase decides what the candidates are.\nOmit it for no narrowing.\n\nIf you don't need a phrase at all, use `POST /events/filter`: a structured\nquery on its own is faster and more predictable than one bolted to a search.\n\n`limit` caps the result size (default 100, max 1000).\n",
        "operationId": "search_1",
        "requestBody": {
          "description": "Search phrase, optional filter, optional limit.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchBodyEventFilter"
              },
              "example": {
                "search": {
                  "query": "bearing"
                },
                "filter": {
                  "type": [
                    "Alarm"
                  ],
                  "status": [
                    "OPEN"
                  ]
                },
                "limit": 50
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Events ranked by how well they match the search phrase.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Event Collection"
                }
              }
            }
          }
        }
      }
    },
    "/events/filter": {
      "post": {
        "tags": [
          "Events"
        ],
        "summary": "Filter events",
        "description": "Return events that match a set of filters. All filters are combined with\nAND — an event must match every filter you supply to be included.\n\nEvery list field also accepts a bare value, so `\"type\": \"alarm\"` and\n`\"type\": [\"alarm\"]` mean the same thing.\n\nCommon filters:\n- `type` / `subType` / `status` / `externalId` / `source` — pattern\n  lists. `*` and `%` are wildcards, `_` is literal, and an entry without a\n  wildcard matches exactly. Entries within a list OR together, so\n  `\"type\": [\"alarm\", \"warning\"]` is one call. Matching is case-insensitive\n  except for a **literal `externalId` entry**, which resolves through the\n  stored hash and so matches the id verbatim — add a trailing `*` if you want\n  case-insensitive matching there too.\n- `eventTime.min` / `eventTime.max` — bound when the event occurred (epoch ms).\n  `createdTime` and `lastUpdatedTime` take the same shape.\n- `dataSetId` — restrict to events in these data sets, each given by `id` or\n  `externalId`: `[{\"id\": 43}, {\"externalId\": \"data_set_sap\"}]`. A data set\n  stands in for **everything beneath it** in the hierarchy, so naming a parent\n  covers its children; an `externalId` naming no data set contributes nothing.\n  Omit the field for no data set restriction; an explicit `[]` matches nothing.\n- `relatedResources` — events attached to specific resources. Each entry takes\n  an `id`, an `externalId`, or both; the event must be attached to all of them.\n- `metadata` — all entries must be present. A **null value matches the key\n  alone**, so `{\"health\": null}` finds anything tagged `health`.\n\n### Ordering and paging\nResults are ordered by `eventTime`, then by `id` to break ties, and capped by\n`limit` (default 1000, max 10000). `sort` takes one property — `eventTime`,\n`createdTime`, `lastUpdatedTime`, `externalId`, `type`, `subType`, `status`,\n`source` or `dataSetId` — with `order` of `asc` or `desc`. `id` is always\nappended, so the order is total and a page boundary can never fall inside a run\nof equal values.\n\nThe response carries `nextCursor` when there may be more. Send it back as\n`cursor` to get the following page, and keep going while it is present — an\nabsent `nextCursor` means the walk is done. This is keyset paging, not `OFFSET`:\neach page is a range the index seeks straight to rather than rows counted and\nthrown away, so page 100 costs what page 1 does, and rows written elsewhere in\nthe table cannot shift the walk into repeating or skipping one.\n\nSend the cursor back with **the same `sort` it came from**. A cursor is a\nposition in one particular order, so continuing it under a different sort is\nrejected with `400` rather than answered with a page that is quietly wrong.\nSorting by `subType` or `status` cannot be paged at all: those fields may be\nempty, and a keyset boundary on them would skip the events that have no value.\n\nA cursor that cannot be read — truncated, edited, or from an older format — is\nrejected with `400` rather than quietly returning the first page, which would\nloop a client that pages by echoing back what it was given.\n\nNote the default differs from `/datasets/filter`, `/resources/filter` and\n`/timeseries/filter`, which return newest-created-first: events are partitioned\nby event time, so ordering by anything else would sort every matched row.\n",
        "operationId": "filter_2",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventRetreiver"
              },
              "example": {
                "limit": 100,
                "filter": {
                  "type": [
                    "alarm",
                    "warning"
                  ],
                  "status": [
                    "OPEN"
                  ],
                  "externalId": [
                    "work_order_*"
                  ],
                  "dataSetId": [
                    {
                      "id": "43"
                    },
                    {
                      "externalId": "data_set_sap"
                    }
                  ],
                  "relatedResources": [
                    {
                      "externalId": "klp_pipe_ws_a1212_dl"
                    }
                  ],
                  "metadata": {
                    "health": null
                  },
                  "eventTime": {
                    "min": 1745241600000
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Events that match every supplied filter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Event Collection"
                }
              }
            }
          }
        }
      }
    },
    "/events/create": {
      "post": {
        "tags": [
          "Events"
        ],
        "summary": "Create events",
        "description": "Create one or more **events**. An event is a timestamped record of\nsomething that happened — an alarm, a calibration, a threshold breach.\n\nEach event needs:\n- a unique `externalId` within your tenant,\n- an `eventTime` (when it happened, epoch ms),\n- optionally `type` and `subType` for categorization,\n- optionally `dataSetId` to group with related data,\n- optionally `relatedResources` to link the event to the resources it\n  concerns. Give each entry an `id`, an `externalId`, or both — the server\n  resolves the missing side and always returns both.\n\n### Servers assigns the id\n`id` is a UUID generated on the server. Don't send one — it will be\nignored. The returned event carries the new id.\n\n### All-or-nothing\nIf any event in the request fails validation, none are created.\n",
        "operationId": "create_4",
        "requestBody": {
          "description": "Events to create.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event Collection"
              },
              "examples": {
                "An alarm attached to a pipe": {
                  "description": "An alarm attached to a pipe",
                  "value": {
                    "items": [
                      {
                        "externalId": "alarm_pipe_overpressure_2026_04_22_14_30",
                        "eventTime": 1745328600000,
                        "type": "alarm",
                        "subType": "overpressure",
                        "description": "Pipe A1212 briefly exceeded 40 bar",
                        "relatedResources": [
                          {
                            "externalId": "klp_pipe_ws_a1212_dl"
                          }
                        ],
                        "dataSetId": 12,
                        "metadata": {
                          "severity": "high"
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "The created events, with server-assigned UUIDs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Event Collection"
                }
              }
            }
          },
          "400": {
            "description": "The request has a problem the server spotted before saving. Typical causes: missing `externalId`, referenced `dataSetId` doesn't exist, a `relatedResources` entry points at a resource that doesn't exist, or its `id` and `externalId` name different resources.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          },
          "409": {
            "description": "An event with one of the `externalId`s already exists. Pick a different one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DuplicateError"
                }
              }
            }
          },
          "422": {
            "description": "One or more fields failed validation rules.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          }
        }
      }
    },
    "/events/byids": {
      "post": {
        "tags": [
          "Events"
        ],
        "summary": "Find events by id or externalId",
        "description": "Look up several events in one call. Each entry in `items[]` carries either\na UUID `id`, an `externalId`, or both — mix freely.\n\nEvents that don't exist are silently omitted. Hard-capped at 10 000 ids\nper request.\n",
        "operationId": "findByIdList_2",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UUIDAndExternalIdCollectionDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "id": "0193a4b5-6c7d-7e8f-9012-3456789abcde"
                  },
                  {
                    "externalId": "alarm_pipe_overpressure_2026_04"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The events that were found. Missing ones are silently left out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Event Collection"
                }
              }
            }
          }
        }
      }
    },
    "/edges/types/create": {
      "post": {
        "tags": [
          "Relationships"
        ],
        "summary": "Create relationship types",
        "description": "Register new relationship type names up-front. Relationship types are\nnormally auto-created the first time they're used in\n`POST /resources/create`; this endpoint is for admin flows that want to\npre-seed the catalog or attach a description / i18n code to a type.\n\nType names are case-insensitive and normalised to uppercase snake case\n(`Flows To` → `FLOWS_TO`).\n",
        "operationId": "createRelationTypes",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Relationship Type Form Collection"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The relationship types were created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Relationship Type Collection"
                }
              }
            }
          },
          "400": {
            "description": "A type name was rejected — for example one that normalises down to nothing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          },
          "409": {
            "description": "A relationship type with the same (case-insensitive) name already exists."
          }
        }
      }
    },
    "/edges/create": {
      "post": {
        "tags": [
          "Relationships"
        ],
        "summary": "Create relationships",
        "description": "Link resources that already exist. Identify each endpoint by either its\nnumeric `fromId`/`toId` or its `fromExternalId`/`toExternalId`, and name the\nrelationship with `relationshipType` (or `relationshipTypeId`). An unknown\ntype name is created on the fly.\n\nTo create the resources **and** their links in one atomic call, use\n`POST /resources/create` instead — this endpoint only connects resources\nthat are already there.\n\n### Rules\n- A relation whose target is a **data set** must use `BELONGS_TO` — that is\n  what membership means, and any other type would look like structure while\n  meaning nothing to the hierarchy.\n- A **time series** belongs to a single data set, so it cannot be connected\n  to a second one.\n- You need write access to the data sets of **both** endpoints.\n\n### All-or-nothing\nIf any relation in the request fails, none of them are created.\n",
        "operationId": "create_5",
        "requestBody": {
          "description": "The relationships to create.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Relationship Form Collection"
              },
              "examples": {
                "Pipe flows to valve": {
                  "description": "Pipe flows to valve",
                  "value": {
                    "items": [
                      {
                        "fromExternalId": "klp_pipe_ws_a1212_dl",
                        "toExternalId": "klp_valve_v9",
                        "relationshipType": "FLOWS_TO",
                        "description": "Downstream leg"
                      }
                    ]
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "The relationships were created, with their server-assigned ids.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Relationship Collection"
                },
                "example": {
                  "items": [
                    {
                      "id": "341",
                      "start": 5677892,
                      "end": 5677893,
                      "type": "FLOWS_TO",
                      "relationshipTypeId": "88"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The request has a problem the server could spot before saving anything — an endpoint that doesn't exist, a missing relationship type, or a relation the graph rules forbid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                },
                "example": {
                  "error": {
                    "code": 400,
                    "message": "Could not find fromNode",
                    "fields": [
                      {
                        "externalId": "klp_valve_v9",
                        "id": "null"
                      }
                    ]
                  }
                }
              }
            }
          },
          "403": {
            "description": "You lack write access to the data set of one of the endpoints."
          },
          "409": {
            "description": "The same relationship already exists between these two resources."
          }
        }
      }
    },
    "/edges/byids": {
      "post": {
        "tags": [
          "Relationships"
        ],
        "summary": "Find relationships by id, with endpoints",
        "description": "Look up several relationships in one call. The response includes each\nrelationship and the two resources it connects (as `nodes[]`) — saves you\na follow-up call to resolve resource details.\n",
        "operationId": "byIds",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Found relationships plus the resources they connect.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GraphResources"
                }
              }
            }
          },
          "404": {
            "description": "None of the given ids match a relationship.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "Could not find edges for the given ids"
                }
              }
            }
          }
        }
      }
    },
    "/datasets/update": {
      "post": {
        "tags": [
          "Data sets"
        ],
        "summary": "Update datasets",
        "description": "Change fields on existing datasets. Identify each one by `id` or\n`externalId`. Only fields you name in the `update` block are changed.\n\nThe same `set` / `setNull` / `add` / `remove` rules apply as for resources\n— see `POST /resources/update` for details, including the 400 on `setNull`\nagainst `name` and `externalId`.\n",
        "operationId": "update_5",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DataSetFormDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "externalId": "sap_work_orders",
                    "update": {
                      "description": {
                        "set": "SAP work orders — live sync"
                      }
                    }
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The datasets after the update, with current values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Data Set Collection"
                }
              }
            }
          },
          "400": {
            "description": "Dataset not found or update rules malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          },
          "409": {
            "description": "The new `externalId` already belongs to another dataset.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DuplicateError"
                }
              }
            }
          }
        }
      }
    },
    "/datasets/search": {
      "post": {
        "tags": [
          "Data sets"
        ],
        "summary": "Full-text search datasets",
        "description": "Free-text search across data sets. The phrase is matched against `name`,\n`externalId` and `description`. Matching is fuzzy and word-aware, and results\nare ranked by relevance.\n\n### Narrowing the results\n`filter` is optional and takes the same criteria as `POST /datasets/filter`.\nIt only ever *removes* matches — the phrase decides what the candidates are.\nOmit it for no narrowing.\n\nIf you don't need a phrase at all, use `POST /datasets/filter`: a structured\nquery on its own is faster and more predictable than one bolted to a search.\n\n`limit` caps the result size (default 100, max 1000). No match is an empty\nlist, not an error.\n\n### Result order\nRanked by relevance (`ts_rank`), strongest match first, with `id` as a\ntie-break so equal-scoring rows keep a stable order and repeated identical\nrequests agree. Ranking means the database scores and sorts every match before\napplying `limit`, so a very broad phrase costs more than a narrow one.\n",
        "operationId": "get_1",
        "requestBody": {
          "description": "Search phrase, optional filter, optional limit.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchBodyData Set Query Filter"
              },
              "example": {
                "search": {
                  "query": "work order"
                },
                "filter": {
                  "metadata": {
                    "source_system": "sap"
                  }
                },
                "limit": 50
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Datasets ranked by how well they match the search phrase.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Data Set Collection"
                }
              }
            }
          },
          "400": {
            "description": "The request failed validation — usually a missing or too-short `search.query`. Response lists the offending fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          }
        }
      }
    },
    "/datasets/list": {
      "post": {
        "tags": [
          "Data sets"
        ],
        "summary": "List all datasets",
        "description": "List the datasets in your tenant, newest first.\n\nDatasets are a small, slow-changing set per tenant (typically one per\ningestion pipeline), so listing them all is cheap — send an empty body\n(`{}`) and you get the lot, up to `limit`.\n\nThis takes the same body as `POST /datasets/filter` and behaves identically;\n`/filter` is the name the resource, timeseries and event endpoints use for\nthe same operation.\n",
        "operationId": "list",
        "requestBody": {
          "description": "Optional filter criteria and limit. An empty object lists everything.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Data Set Query"
              },
              "example": {
                "limit": 100
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The datasets in your tenant, capped at `limit`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Data Set Collection"
                }
              }
            }
          },
          "400": {
            "description": "The request failed validation — typically a `limit` above 10000.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "limit: must be less than or equal to 10000"
                }
              }
            }
          }
        }
      }
    },
    "/datasets/filter": {
      "post": {
        "tags": [
          "Data sets"
        ],
        "summary": "Filter datasets",
        "description": "Structured filtering over datasets. Every criterion is optional and they\nAND together, so an empty `filter` returns every dataset — the same thing\n`POST /datasets/list` does.\n\n* `id` — datasets named directly by id. An empty list places no restriction.\n* `externalId` / `name` / `source` — pattern lists, OR-ed within each list.\n  `*` and `%` are both wildcards, so `[\"sap_work_orders\", \"plant_*\"]` mixes an\n  exact id with a prefix search, and `[\"*_archive\"]` is a suffix one. An entry\n  with no wildcard matches exactly. `_` is literal — external ids are built out\n  of underscores, so it has to be. All three match case-insensitively.\n* `labels` — datasets carrying **all** of these labels. Names are canonicalised,\n  so `pump a` finds the label stored as `PUMP_A`.\n* `metadata` — every key/value pair given must be present on the dataset.\n* `createdTime` / `lastUpdatedTime` — inclusive `min`/`max` instants.\n\nFor free-text matching over name and description use\n`POST /datasets/search` instead.\n\nResults come newest created first, capped by `limit` (default 1000, max 10000).\n\n`sort` takes one property — `id`, `externalId`, `name`, `source`,\n`description`, `createdTime`, `lastUpdatedTime` or `dataSetId` — with `order` of\n`asc` or `desc`; `id` is always appended so the order is total. The response\ncarries `nextCursor` when there may be more: send it back as `cursor`, with the\nsame `sort` it came from, and keep going while it is present. Keyset paging, not\n`OFFSET`, so a deep page costs what a shallow one does.\n",
        "operationId": "filter_3",
        "requestBody": {
          "description": "Filter criteria and optional limit.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Data Set Query"
              },
              "example": {
                "limit": 100,
                "filter": {
                  "name": [
                    "SAP*"
                  ],
                  "externalId": [
                    "sap_*"
                  ],
                  "source": [
                    "sap",
                    "opc_*"
                  ],
                  "metadata": {
                    "owner": "plant-a"
                  },
                  "createdTime": {
                    "min": "2026-01-01T00:00:00Z"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The datasets matching every supplied criterion. Empty `items[]` means nothing matched.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Data Set Collection"
                }
              }
            }
          },
          "400": {
            "description": "The request failed validation — typically a `limit` above 10000.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "limit: must be less than or equal to 10000"
                }
              }
            }
          }
        }
      }
    },
    "/datasets/create": {
      "post": {
        "tags": [
          "Data sets"
        ],
        "summary": "Create datasets",
        "description": "Create one or more **datasets**. A dataset is a container that groups\nresources and timeseries by their origin — typically one dataset per\ningestion pipeline (\"SAP work orders\", \"Plant A telemetry\", etc.).\n\nEach dataset needs a unique `externalId` within your tenant, a `name`, and\noptionally a `description`. Once created, refer to it from resources and\ntimeseries via their `dataSetId` field to group them.\n",
        "operationId": "create_6",
        "requestBody": {
          "description": "Datasets to create.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Data Set Collection"
              },
              "example": {
                "items": [
                  {
                    "externalId": "sap_work_orders",
                    "name": "SAP Work Orders",
                    "description": "Work orders mirrored from SAP every 15 minutes"
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "The created datasets with server-assigned `id`s.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Data Set Collection"
                }
              }
            }
          },
          "400": {
            "description": "The request has a problem the server spotted before saving. The `fields` list tells you which input was wrong.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                }
              }
            }
          },
          "409": {
            "description": "A dataset with one of the `externalId`s already exists. Pick a different one, or use `POST /datasets/update`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DuplicateError"
                }
              }
            }
          }
        }
      }
    },
    "/datasets/byids": {
      "post": {
        "tags": [
          "Data sets"
        ],
        "summary": "Find datasets by id or externalId",
        "description": "Look up several datasets in one call. Each entry in `items[]` carries\neither a numeric `id`, an `externalId`, or both.\n\nDatasets that don't exist are silently omitted. Compare the returned items\nagainst what you asked for to detect missing ones.\n",
        "operationId": "byIds_1",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdCollectionDataWrapper"
              },
              "example": {
                "items": [
                  {
                    "externalId": "sap_work_orders"
                  },
                  {
                    "id": 12
                  }
                ]
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The datasets that were found. Missing ones are silently left out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Data Set Collection"
                }
              }
            }
          }
        }
      }
    },
    "/units": {
      "get": {
        "tags": [
          "Units"
        ],
        "summary": "List all units",
        "description": "Return every unit available to your tenant. The list is bounded (typical size ~100s) and fully cacheable.",
        "operationId": "list_1",
        "responses": {
          "200": {
            "description": "Returns a list of units",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Unit"
                }
              }
            }
          }
        }
      }
    },
    "/units/{externalId}": {
      "get": {
        "tags": [
          "Units"
        ],
        "summary": "Find a unit by externalId",
        "description": "Look up a single unit by its `externalId` (e.g. `celsius`, `kilogram`). Returns 404 if no unit has this externalId.",
        "operationId": "findByExternalId",
        "parameters": [
          {
            "name": "externalId",
            "in": "path",
            "description": "External id of the unit.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "temperature:deg_c"
          }
        ],
        "responses": {
          "200": {
            "description": "The unit if it was found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Unit"
                }
              }
            }
          },
          "404": {
            "description": "No unit with this externalId exists.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              },
              "application/xml": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/tenant/features": {
      "get": {
        "tags": [
          "Tenant"
        ],
        "summary": "Get feature flags for your tenant",
        "description": "Return a map of optional features and whether they're enabled for your\ntenant. Clients should use this to gate feature-specific UI and API calls\n— disabled features may still have endpoints that return 404 or 403.\n",
        "operationId": "getFeatures",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/TenantFeatures"
                }
              }
            }
          }
        }
      }
    },
    "/resources/{id}": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "Find resource by id",
        "description": "Look up a single resource by its numeric server-assigned `id`.\n\nIf you only know the `externalId`, use `POST /resources/byids` instead —\nit accepts either kind of identifier.\n",
        "operationId": "get_3",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Numeric id of the resource.",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5677892
          }
        ],
        "responses": {
          "200": {
            "description": "The resource was found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Resource Collection"
                },
                "example": {
                  "items": [
                    {
                      "id": 5677892,
                      "externalId": "klp_pipe_ws_a1212_dl",
                      "name": "klp pipe ws-a1212-dl",
                      "description": "Water stream pipe",
                      "labels": [
                        "PIPE"
                      ],
                      "dataSetId": 12,
                      "source": "dolphin_rex_pipes",
                      "metadata": {
                        "work_order": "wo-sap-12344"
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "No resource with this `id` exists, or it belongs to a tenant you can't read. Double-check the id and your API token's tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "Could not find resource with id: 42"
                }
              }
            }
          }
        }
      }
    },
    "/policies": {
      "get": {
        "tags": [
          "Policies"
        ],
        "summary": "List all Policy nodes",
        "description": "Returns all Policy nodes currently stored in the system.",
        "operationId": "listPolicies",
        "responses": {
          "200": {
            "description": "List of all Policy nodes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Policy Collection"
                }
              }
            }
          }
        }
      }
    },
    "/policies/{policyNodeId}": {
      "get": {
        "tags": [
          "Policies"
        ],
        "summary": "Get a single Policy node by id",
        "operationId": "getPolicyById",
        "parameters": [
          {
            "name": "policyNodeId",
            "in": "path",
            "description": "Numeric id of the policy node.",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5677892
          }
        ],
        "responses": {
          "200": {
            "description": "The policy node.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Policy Collection"
                }
              }
            }
          },
          "404": {
            "description": "No policy node with this id exists.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/policies/types": {
      "get": {
        "tags": [
          "Policies"
        ],
        "summary": "List all unique Policy types",
        "description": "Returns all available Policy definitions (e.g., IS_WRITE_PROTECTED,\nMASKING_POLICY, HAS_REQUIREMENT, etc.) based on the PolicyType enum.\nUsed by the UI's 'Policies' section.",
        "operationId": "listPolicyTypes",
        "responses": {
          "200": {
            "description": "List of Policy types.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Policy Collection"
                }
              }
            }
          }
        }
      }
    },
    "/labels": {
      "get": {
        "tags": [
          "Labels"
        ],
        "summary": "List all labels",
        "description": "Return every label in your tenant. Labels are a small, slow-changing set so listing them all is cheap.",
        "operationId": "list_3",
        "responses": {
          "200": {
            "description": "A collection with label objects is returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label Collection"
                }
              }
            }
          }
        }
      }
    },
    "/labels/{id}": {
      "get": {
        "tags": [
          "Labels"
        ],
        "summary": "Get label by id",
        "description": "Look up a single label by its numeric `id`. Returns 404 if no label has this id.",
        "operationId": "get_4",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Numeric id of the label.",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5677892
          }
        ],
        "responses": {
          "200": {
            "description": "A single label object is returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label Collection"
                }
              }
            }
          },
          "404": {
            "description": "No label with this id exists.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/governance/templates": {
      "get": {
        "tags": [
          "Governance"
        ],
        "summary": "List all governance templates",
        "description": "Return every governance template available to your tenant.",
        "operationId": "listTemplates",
        "responses": {
          "200": {
            "description": "List of governance templates",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Governance Template Collection"
                }
              }
            }
          }
        }
      }
    },
    "/governance/templates/{templateId}": {
      "get": {
        "tags": [
          "Governance"
        ],
        "summary": "Get a governance template by id",
        "description": "Look up one governance template by its numeric `templateId`.",
        "operationId": "getTemplateById",
        "parameters": [
          {
            "name": "templateId",
            "in": "path",
            "description": "Numeric id of the governance template.",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 12
          }
        ],
        "responses": {
          "200": {
            "description": "Governance template",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Governance Template Collection"
                }
              }
            }
          }
        }
      }
    },
    "/functions/list": {
      "get": {
        "tags": [
          "Functions"
        ],
        "summary": "List functions",
        "description": "List all functions for the current tenant.",
        "operationId": "listFunctions",
        "responses": {
          "200": {
            "description": "Functions returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Function Collection"
                }
              }
            }
          }
        }
      }
    },
    "/files/trash": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "List deleted files",
        "description": "The soft-deleted files in the tenant trash that the caller can read. Their name and path are the pre-deletion values; the deletion time is encoded in the externalId (DELETED_..._<epochMillis>). Restore them via POST /files/restore.",
        "operationId": "listTrash",
        "responses": {
          "200": {
            "description": "Deleted files in the trash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/File Collection"
                }
              }
            }
          }
        }
      }
    },
    "/files/search": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Search files and folders",
        "description": "Case-insensitive full-text search over file and folder names (and descriptions)\nacross the whole tree. Results are narrowed to the caller's readable datasets.\n\nUnlike the resource, data set, timeseries and event searches this one is a `GET`\ntaking the phrase as `q`, and it has no structured `filter`: files are indexed\nby the tree they live in rather than by the node criteria those four share. Use\n`GET /files/list` to walk a folder.\n\n`limit` caps the result size (default 100, max 1000), matching the cap the other\nsearches apply.",
        "operationId": "search_2",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Text to match against file and folder names.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "invoice"
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Cap on results returned. Defaults to 100, max 1000.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32"
            },
            "example": 50
          }
        ],
        "responses": {
          "200": {
            "description": "Matching files and folders.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/File Collection"
                }
              }
            }
          }
        }
      }
    },
    "/files/list/**": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "List contents of a folder",
        "description": "Lists the files and folders directly under the given folder path. The path is taken from the URL after /files/list (e.g. GET /files/list/docs/2024); an empty path lists the root. Results are limited to the caller's readable datasets; files and folders with no dataset are public.",
        "operationId": "listDirectory",
        "responses": {
          "200": {
            "description": "Folders and files found in the submitted folder.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/File Collection"
                }
              }
            }
          }
        }
      }
    },
    "/files/list": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "List contents of a folder",
        "description": "Lists the files and folders directly under the given folder path. The path is taken from the URL after /files/list (e.g. GET /files/list/docs/2024); an empty path lists the root. Results are limited to the caller's readable datasets; files and folders with no dataset are public.",
        "operationId": "listDirectory_1",
        "responses": {
          "200": {
            "description": "Folders and files found in the submitted folder.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/File Collection"
                }
              }
            }
          }
        }
      }
    },
    "/files/download/{id}": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Download file",
        "description": "Download file",
        "operationId": "download",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Numeric id of the file to download.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": 5677892
          }
        ],
        "responses": {
          "200": {
            "description": "The file content as a binary stream.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    },
    "/events/{id}": {
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "Find event by id",
        "description": "Look up a single event by its UUID.\n\nAn event is a timestamped thing-that-happened: an alarm triggered, a\nmaintenance job ran, a measurement crossed a threshold. Events are\nimmutable in practice — prefer creating a new event to updating one.\n",
        "operationId": "get_5",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "UUID of the event to look up.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "0195f3a2-4c1b-7f9e-9c3a-1b2d4e6f8a90"
          }
        ],
        "responses": {
          "200": {
            "description": "The event was found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Event Collection"
                }
              }
            }
          },
          "404": {
            "description": "No event with this id exists, or it belongs to a tenant you can't read.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "Could not find event with id: 0195f3a2-..."
                }
              }
            }
          }
        }
      }
    },
    "/events/search/type": {
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "Search event types",
        "description": "Return distinct `type` values containing `q` (case-insensitive substring), sorted\nalphabetically and restricted to your readable datasets — built for type-ahead.\n`limit` caps the result (default 1000). To list every value, use\n`GET /events/list/types`.\n",
        "operationId": "searchTypes",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Case-insensitive substring to match against the values.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "alarm"
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of distinct values to return. Capped at 10000.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1000
            },
            "example": 1000
          }
        ],
        "responses": {
          "200": {
            "description": "Distinct event types matching the query.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Value Collection"
                }
              }
            }
          },
          "400": {
            "description": "Missing required `q` query parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              },
              "application/xml": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/events/search/sub-type": {
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "Search event sub-types",
        "description": "Return distinct `subType` values containing `q` (case-insensitive substring),\nsorted alphabetically and restricted to your readable datasets. `limit` caps the\nresult (default 1000). To list every value, use `GET /events/list/sub-types`.\n",
        "operationId": "searchSubTypes",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Case-insensitive substring to match against the values.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "alarm"
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of distinct values to return. Capped at 10000.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1000
            },
            "example": 1000
          }
        ],
        "responses": {
          "200": {
            "description": "Distinct event sub-types matching the query.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Value Collection"
                }
              }
            }
          },
          "400": {
            "description": "Missing required `q` query parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              },
              "application/xml": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/events/search/status": {
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "Search event statuses",
        "description": "Return distinct `status` values containing `q` (case-insensitive substring), sorted\nalphabetically and restricted to your readable datasets. `limit` caps the result\n(default 1000). To list every value, use `GET /events/list/statuses`.\n",
        "operationId": "searchStatuses",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Case-insensitive substring to match against the values.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "alarm"
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of distinct values to return. Capped at 10000.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1000
            },
            "example": 1000
          }
        ],
        "responses": {
          "200": {
            "description": "Distinct event statuses matching the query.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Value Collection"
                }
              }
            }
          },
          "400": {
            "description": "Missing required `q` query parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              },
              "application/xml": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/events/search/source": {
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "Search event sources",
        "description": "Return distinct `source` values containing `q` (case-insensitive substring), sorted\nalphabetically and restricted to your readable datasets. `limit` caps the result\n(default 1000). To list every value, use `GET /events/list/sources`.\n",
        "operationId": "searchSources",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Case-insensitive substring to match against the values.",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "alarm"
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of distinct values to return. Capped at 10000.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1000
            },
            "example": 1000
          }
        ],
        "responses": {
          "200": {
            "description": "Distinct event sources matching the query.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Value Collection"
                }
              }
            }
          },
          "400": {
            "description": "Missing required `q` query parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              },
              "application/xml": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/events/list/types": {
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "List event types",
        "description": "Return every distinct `type` value present on events you can read, sorted\nalphabetically and restricted to the datasets your token grants read access to.\n`limit` caps the result (default 1000). To substring-match instead of listing all,\nuse `GET /events/search/type`.\n",
        "operationId": "listTypes",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of distinct values to return. Capped at 10000.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1000
            },
            "example": 1000
          }
        ],
        "responses": {
          "200": {
            "description": "Distinct event types.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Value Collection"
                }
              }
            }
          }
        }
      }
    },
    "/events/list/sub-types": {
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "List event sub-types",
        "description": "Return every distinct `subType` value present on events you can read, sorted\nalphabetically and restricted to your readable datasets. `limit` caps the result\n(default 1000). For substring matching use `GET /events/search/sub-type`.\n",
        "operationId": "listSubTypes",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of distinct values to return. Capped at 10000.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1000
            },
            "example": 1000
          }
        ],
        "responses": {
          "200": {
            "description": "Distinct event sub-types.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Value Collection"
                }
              }
            }
          }
        }
      }
    },
    "/events/list/statuses": {
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "List event statuses",
        "description": "Return every distinct `status` value present on events you can read, sorted\nalphabetically and restricted to your readable datasets. `limit` caps the result\n(default 1000). For substring matching use `GET /events/search/status`.\n",
        "operationId": "listStatuses",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of distinct values to return. Capped at 10000.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1000
            },
            "example": 1000
          }
        ],
        "responses": {
          "200": {
            "description": "Distinct event statuses.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Value Collection"
                }
              }
            }
          }
        }
      }
    },
    "/events/list/sources": {
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "List event sources",
        "description": "Return every distinct `source` value present on events you can read, sorted\nalphabetically and restricted to your readable datasets. `limit` caps the result\n(default 1000). For substring matching use `GET /events/search/source`.\n",
        "operationId": "listSources",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of distinct values to return. Capped at 10000.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1000
            },
            "example": 1000
          }
        ],
        "responses": {
          "200": {
            "description": "Distinct event sources.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Value Collection"
                }
              }
            }
          }
        }
      }
    },
    "/events/count": {
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "Count events",
        "description": "Return the total number of events in your tenant as `{ \"count\": N }`.\nCheap — runs as a single query. Does not support filters; use\n`POST /events/filter` with `limit` for filtered counting.\n",
        "operationId": "count",
        "responses": {
          "200": {
            "description": "Total event count for your tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Event Count"
                },
                "example": {
                  "count": 148392
                }
              }
            }
          }
        }
      }
    },
    "/edges/{id}": {
      "get": {
        "tags": [
          "Relationships"
        ],
        "summary": "Find relationship by id",
        "description": "Look up a single relationship (edge) between two resources by its numeric `id`.",
        "operationId": "get_6",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Numeric id of the relationship to look up.",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5677892
          }
        ],
        "responses": {
          "200": {
            "description": "The relationship was found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Relationship Collection"
                }
              }
            }
          },
          "404": {
            "description": "No relationship with this id exists.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "string",
                  "example": "Could not find edge with id: 42"
                }
              }
            }
          }
        }
      }
    },
    "/edges/types": {
      "get": {
        "tags": [
          "Relationships"
        ],
        "summary": "List relationship types",
        "description": "Return every relationship type your tenant has defined (`FLOWS_TO`,\n`CONNECTS_TO`, `PART_OF`, etc.). Relationship types are created on demand\nby `POST /resources/create` when you first use a new type name, or\nexplicitly via `POST /edges/types/create`.\n",
        "operationId": "getRelationTypes",
        "responses": {
          "200": {
            "description": "Every relationship type available to your tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Relationship Type Collection"
                }
              }
            }
          }
        }
      }
    },
    "/datasets/{id}": {
      "get": {
        "tags": [
          "Data sets"
        ],
        "summary": "Get a data set by id",
        "description": "Look up one data set by its numeric `id`.\n\nTo look one up by `externalId`, or several at once, use `POST /datasets/byids`.\n",
        "operationId": "get_7",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Numeric id of the data set.",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5677892
          }
        ],
        "responses": {
          "200": {
            "description": "The data set was found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Data Set Collection"
                }
              }
            }
          },
          "404": {
            "description": "No data set with this `id` exists.",
            "content": {
              "application/problem+json": {}
            }
          }
        }
      }
    },
    "/datasets/policies": {
      "get": {
        "tags": [
          "Data sets"
        ],
        "summary": "List policies available to datasets",
        "description": "Return every access policy that a dataset can be associated with. Use this\nwhen building a dataset form and you need to show the user the policies\nthey can pick from.\n",
        "operationId": "listPolicyNodes",
        "responses": {
          "200": {
            "description": "Every policy in your tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Policy Collection"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "IdCollection": {
        "type": "object",
        "description": "IdCollection object that contains id or external id.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the object,",
            "example": 5677892
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the object,",
            "example": "kl_33PP3_sensor_alarm_temperature"
          }
        }
      },
      "IdCollectionDataWrapper": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "description": "Add either id or external id or a mix of both to the request body.",
            "items": {
              "$ref": "#/components/schemas/IdCollection"
            }
          }
        }
      },
      "BadRequestError": {
        "type": "object",
        "description": "Bad request, validation error with submitted fields.",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32"
          },
          "message": {
            "type": "string"
          },
          "fields": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": {
                "type": "string"
              }
            }
          }
        }
      },
      "ConflictError": {
        "type": "object",
        "description": "Concurrency conflict — the targeted resource was modified or removed by another request between read and write. Clients should re-fetch the current state and retry.",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32"
          },
          "cause": {
            "type": "string",
            "description": "Machine-readable conflict cause.",
            "enum": [
              "concurrency"
            ],
            "example": "concurrency"
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation intended for logs and UIs.",
            "example": "The resource was modified or removed by another request. Re-read and retry."
          }
        }
      },
      "Policy": {
        "type": "object",
        "description": "Add policies to the data set.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the object.",
            "example": 5677892
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the object.",
            "example": "klp_pipe_ws_a1212_dl",
            "maxLength": 256,
            "minLength": 3
          },
          "name": {
            "type": "string",
            "description": "The name of the object.",
            "example": "klp pipe ws-a1212-dl",
            "maxLength": 512,
            "minLength": 3
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Entity specific metadata. A key-value store.",
            "example": {
              "work_order": "wo-sap-12344"
            }
          },
          "description": {
            "type": "string",
            "description": "The description of the object.",
            "example": "Water stream pipe"
          },
          "source": {
            "type": "string",
            "description": "The name of the data source containing the primary information about the object.",
            "example": "dolphin_rex_pipes",
            "pattern": "^$|.{2,128}"
          },
          "dataSetId": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the data set this node belongs to.",
            "example": 12
          },
          "labels": {
            "type": "array",
            "description": "A list of the labels associated with this node.",
            "example": [
              "resource",
              "PIPE"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 2147483647,
            "minItems": 1
          },
          "relatedResources": {
            "type": "array",
            "description": "Nodes this node is connected to, with relationship type and direction.",
            "example": [
              {
                "id": 34,
                "externalId": "sensor_abc",
                "relationshipType": "PUBLISHES_DATA_TO",
                "direction": "OUTBOUND"
              }
            ],
            "items": {
              "$ref": "#/components/schemas/RelatedNode"
            }
          },
          "type": {
            "type": "string",
            "description": "If you want to write data to a write-protected data set, you need to be a member of a group that has the \"datasets:owner\" policy for the data set. Read more: [Owner policy docs](https://intellistream.ai/documentation/datasets#owner)\"\n ",
            "enum": [
              "SECURITY_POLICY",
              "ENCRYPTION_POLICY",
              "MASKING_POLICY",
              "IS_WRITE_PROTECTED",
              "IS_READ_PROTECTED",
              "HAS_REQUIREMENT",
              "NAMING_CONVENTION"
            ],
            "example": "IS_WRITE_PROTECTED or IS_READ_PROTECTED or REQUIREMENT"
          },
          "value": {
            "description": "Policy value, can be boolean, text or number",
            "example": "TRUE, FALSE, 1001, 'FOOBAR'"
          },
          "nodeType": {
            "type": "string",
            "description": "Node type (always POLICY when node)"
          },
          "templateId": {
            "type": "integer",
            "format": "int64",
            "description": "Template ID applied to this policy node",
            "example": 3
          },
          "deactivated": {
            "type": "boolean"
          },
          "createdTime": {
            "type": "string",
            "format": "date-time",
            "description": "When the object was created, as an ISO-8601 UTC timestamp.",
            "example": "2024-06-17T12:34:56Z",
            "readOnly": true
          },
          "lastUpdatedTime": {
            "type": "string",
            "format": "date-time",
            "description": "When the object was last updated, as an ISO-8601 UTC timestamp.",
            "example": "2024-06-17T12:34:56Z",
            "readOnly": true
          }
        },
        "required": [
          "externalId",
          "labels",
          "name",
          "type"
        ]
      },
      "Policy Collection": {
        "type": "object",
        "description": "Data response with a collection of policies.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Policy"
            }
          }
        }
      },
      "RelatedNode": {
        "type": "object",
        "description": "A node related to another, with relationship type and direction.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the object,",
            "example": 5677892
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the object,",
            "example": "kl_33PP3_sensor_alarm_temperature"
          },
          "relationshipType": {
            "type": "string",
            "description": "The relationship type of the object,",
            "example": "publishes_data_to"
          },
          "direction": {
            "type": "string",
            "description": "Direction of the relation relative to the node it is attached to.",
            "enum": [
              "OUTBOUND",
              "INBOUND"
            ],
            "example": "OUTBOUND"
          },
          "edgeId": {
            "type": "integer",
            "format": "int64",
            "description": "Id of the edge realizing this relation; look up the edge by this id for its detail/metadata.",
            "example": 98231
          }
        }
      },
      "Label": {
        "type": "object",
        "description": "Label object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the label object.",
            "example": 5677892
          },
          "name": {
            "type": "string",
            "description": "The name of the label object.",
            "example": "NIFI_FUNCTION",
            "maxLength": 128,
            "minLength": 3
          },
          "description": {
            "type": "string",
            "description": "The description of the label object.",
            "example": "A function that polls data from a raw source and transform it."
          },
          "i18nCode": {
            "type": "string",
            "description": "The i18n code of the label object that is used for translation.",
            "example": "nifi.function"
          },
          "color": {
            "type": "string",
            "description": "The color of the label object.",
            "example": "#cc11cc",
            "maxLength": 7,
            "minLength": 0
          }
        },
        "required": [
          "name"
        ]
      },
      "Label Collection": {
        "type": "object",
        "description": "Data Response with Label collection.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Label"
            }
          }
        }
      },
      "UUIDAndExternalIdCollection": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "externalId": {
            "type": "string"
          }
        }
      },
      "UUIDAndExternalIdCollectionDataWrapper": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "description": "Add either id or external id or a mix of both to the request body.",
            "items": {
              "$ref": "#/components/schemas/UUIDAndExternalIdCollection"
            },
            "maxItems": 10000,
            "minItems": 0
          }
        }
      },
      "Delete Datapoint Collection": {
        "type": "object",
        "description": "The list of delete requests to perform.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DeleteDatapoint"
            }
          }
        }
      },
      "DeleteDatapoint": {
        "type": "object",
        "description": "Delete data points request body.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the time series. Give this or `externalId`.",
            "example": 123466453
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the time series. Give this or `id`.",
            "example": "a4545_well_pump_pressure_a"
          },
          "inclusiveBegin": {
            "type": "string",
            "description": "Start of the window to clear, inclusive. Either ISO-8601 or epoch milliseconds. Optional: leave it out to delete everything up to `exclusiveEnd`, and leave both bounds out to clear every data point of the series.",
            "example": "2026-01-01T00:00:00Z"
          },
          "exclusiveEnd": {
            "type": "string",
            "description": "End of the window to clear, exclusive. Either ISO-8601 or epoch milliseconds. Optional: leave it out to delete everything from `inclusiveBegin` onward.",
            "example": "2026-02-01T00:00:00Z"
          }
        }
      },
      "File Collection": {
        "type": "object",
        "description": "Data response with a collection of files and folders.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IndexNode"
            }
          }
        }
      },
      "IndexNode": {
        "type": "object",
        "description": "Index node is either a file or folder.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the index node.",
            "example": 5677892
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the index node.",
            "example": "file_sap_chemicals_csv"
          },
          "type": {
            "type": "string",
            "description": "Index node type, file or folder.",
            "example": "MyDocuments"
          },
          "name": {
            "type": "string",
            "description": "File or folder name",
            "example": "sap_chemicals.csv"
          },
          "path": {
            "type": "string",
            "description": "The path of the node, unix style.",
            "example": "/path/to/foo/bar"
          },
          "description": {
            "type": "string",
            "description": "Index node description",
            "example": "Data pulled daily from Kyoto Systems."
          },
          "size": {
            "type": "integer",
            "format": "int64",
            "description": "File size in bytes.",
            "example": 1024
          },
          "checksum": {
            "type": "string",
            "description": "Hexadecimal representation of the file checksum. The algorithm is SHA-256.",
            "example": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
          },
          "source": {
            "type": "string",
            "description": "Define what is the source for the file.",
            "example": "Kyoto Systems"
          },
          "mimeType": {
            "type": "string",
            "description": "File mime type",
            "example": "pdf"
          },
          "sourceDateCreated": {
            "type": "string",
            "format": "date-time",
            "description": "When file was created.",
            "example": "2000-01-01 12:00"
          },
          "sourceLastUpdated": {
            "type": "string",
            "format": "date-time",
            "description": "When file was last updated.",
            "example": "2000-01-01 18:00"
          },
          "dateCreated": {
            "type": "string",
            "format": "date-time",
            "description": "When file was uploaded to IntelliStream DataHub.",
            "example": "2024-01-01 12:00"
          },
          "lastUpdated": {
            "type": "string",
            "format": "date-time",
            "description": "When file was last updated in IntelliStream DataHub.",
            "example": "2024-01-01 18:00"
          },
          "parentId": {
            "type": "integer",
            "format": "int64",
            "description": "Parent Index Node id, always a folder.",
            "example": "MyDocuments"
          },
          "parentExternalId": {
            "type": "string",
            "description": "Parent Index Node external id, always a folder.",
            "example": "MyDocuments"
          },
          "dataSetId": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the data set for this index node.",
            "example": 2323
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "File or folder metadata, additional fields you can bind information with.",
            "example": {
              "definition": "SAP ORDER PLACED"
            }
          },
          "relatedResources": {
            "type": "array",
            "description": "Collection of id for resources that this file or folder has a relation to.",
            "example": [
              2323,
              34,
              166
            ],
            "items": {
              "type": "integer",
              "format": "int64"
            },
            "uniqueItems": true
          },
          "securityCategories": {
            "type": "array",
            "description": "A collection of security categories.",
            "example": [
              33,
              5,
              128
            ],
            "items": {
              "type": "integer",
              "format": "int32"
            },
            "uniqueItems": true
          },
          "file": {
            "type": "boolean"
          },
          "root": {
            "type": "boolean"
          },
          "folder": {
            "type": "boolean"
          },
          "humanReadableSize": {
            "type": "string"
          }
        }
      },
      "Unit": {
        "type": "object",
        "description": "Unit object, that describes to properties of the unit type.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the unit.",
            "example": 5677892
          },
          "externalId": {
            "type": "string",
            "description": "The parent external id of the unit.",
            "example": "temperature_celsius",
            "maxLength": 256,
            "minLength": 3
          },
          "name": {
            "type": "string",
            "description": "The name of the unit.",
            "example": "Celsius",
            "maxLength": 64,
            "minLength": 1
          },
          "longName": {
            "type": "string",
            "description": "The full name of the unit.",
            "example": "Celsius",
            "maxLength": 256,
            "minLength": 1
          },
          "symbol": {
            "type": "string",
            "description": "The symbol of the unit.",
            "example": "°C",
            "maxLength": 32,
            "minLength": 1
          },
          "description": {
            "type": "string",
            "description": "The description of the unit.",
            "example": "Something about celsius..."
          },
          "aliasNames": {
            "type": "array",
            "description": "A list of alternative aliases (names) for the unit.",
            "example": [
              "c",
              "C",
              "Celsius"
            ],
            "items": {
              "type": "string"
            },
            "uniqueItems": true
          },
          "quantity": {
            "type": "string",
            "description": "Specifies the physical quantity the unit.",
            "example": "Temperature"
          },
          "conversion": {
            "$ref": "#/components/schemas/UnitConversion",
            "description": "An Unit Conversion object with multiplier and offset values for converting between units."
          },
          "source": {
            "type": "string",
            "description": "Source of the unit specification.",
            "example": "qudt.org"
          },
          "sourceReference": {
            "type": "string",
            "description": "Reference to the source of the specification",
            "example": "https://qudt.org/vocab/unit/DEG_C"
          }
        },
        "required": [
          "externalId",
          "name"
        ]
      },
      "UnitConversion": {
        "type": "object",
        "description": "Unit Conversion object, containing multiplier and offset values for converting between units.",
        "properties": {
          "multiplier": {
            "type": "number",
            "format": "double",
            "description": "The multiplier.",
            "example": 1
          },
          "offset": {
            "type": "number",
            "format": "double",
            "description": "The offset.",
            "example": 273.15
          }
        }
      },
      "TimeseriesFields": {
        "type": "object",
        "properties": {
          "name": {
            "$ref": "#/components/schemas/UpdateStringField",
            "description": "The name field."
          },
          "externalId": {
            "$ref": "#/components/schemas/UpdateStringField",
            "description": "The name field."
          },
          "metadata": {
            "$ref": "#/components/schemas/UpdateMapField",
            "description": "The meta data field."
          },
          "unit": {
            "$ref": "#/components/schemas/UpdateStringField",
            "description": "The unit field."
          },
          "unitExternalId": {
            "$ref": "#/components/schemas/UpdateStringField",
            "description": "The unit external id field."
          },
          "description": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "securityCategories": {
            "$ref": "#/components/schemas/UpdateNumberListField"
          },
          "dataSetId": {
            "$ref": "#/components/schemas/UpdateNumberField"
          },
          "source": {
            "$ref": "#/components/schemas/UpdateStringField",
            "description": "The source field."
          }
        }
      },
      "UpdateMapField": {
        "type": "object",
        "description": "What value you want the map field set to.",
        "properties": {
          "set": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "What values you want the map field to. This will remove all existing entries.",
            "example": {
              "topic": "message"
            }
          },
          "add": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "What values you want to add to the map. This will keep all existing entries.",
            "example": {
              "topic": "message"
            }
          },
          "remove": {
            "type": "array",
            "description": "What values you want to remove from the map.",
            "example": {
              "topic": "message"
            },
            "items": {
              "type": "string"
            }
          }
        }
      },
      "UpdateNumberField": {
        "type": "object",
        "description": "What value you want the number field set to",
        "properties": {
          "set": {
            "type": "integer",
            "format": "int64",
            "description": "What value you want the number field set to",
            "example": "Vidar"
          },
          "setNull": {
            "type": "boolean",
            "description": "When you want the value to be null",
            "example": true
          }
        }
      },
      "UpdateNumberListField": {
        "type": "object",
        "description": "What numeric values you want in the list.",
        "properties": {
          "set": {
            "type": "array",
            "description": "What numeric values you want in the list. This will remove all existing entries.",
            "example": [
              122,
              1235
            ],
            "items": {
              "type": "integer",
              "format": "int64"
            }
          },
          "add": {
            "type": "array",
            "description": "What numeric values you want in the list. This will keep all existing entries.",
            "example": [
              122,
              1235
            ],
            "items": {
              "type": "integer",
              "format": "int64"
            }
          },
          "remove": {
            "type": "array",
            "description": "What numeric values you want to remove from the list.",
            "example": [
              122,
              1235
            ],
            "items": {
              "type": "integer",
              "format": "int64"
            }
          }
        }
      },
      "UpdateStringField": {
        "type": "object",
        "description": "What value you want the string field set to",
        "properties": {
          "set": {
            "type": "string",
            "description": "What value you want the string field set to",
            "example": "Vidar"
          },
          "setNull": {
            "type": "boolean",
            "description": "When you want the value to be null",
            "example": true
          }
        }
      },
      "UpdateTimeseries": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the time series.",
            "example": 123466453
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the time series.",
            "example": "a4545_well_pump_pressure_a",
            "pattern": "[A-Za-z0-9._:+=-]+"
          },
          "update": {
            "$ref": "#/components/schemas/TimeseriesFields"
          }
        }
      },
      "UpdateTimeseriesWrapper": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "description": "Update timeseries request body",
            "items": {
              "$ref": "#/components/schemas/UpdateTimeseries"
            }
          }
        }
      },
      "Time Series Collection": {
        "type": "object",
        "description": "Data Response with Time Series as collection.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Timeseries"
            }
          }
        }
      },
      "Timeseries": {
        "type": "object",
        "description": "Timeseries description",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the object.",
            "example": 5677892
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the object.",
            "example": "klp_pipe_ws_a1212_dl",
            "maxLength": 256,
            "minLength": 3
          },
          "name": {
            "type": "string",
            "description": "The name of the object.",
            "example": "klp pipe ws-a1212-dl",
            "maxLength": 512,
            "minLength": 3
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Entity specific metadata. A key-value store.",
            "example": {
              "work_order": "wo-sap-12344"
            }
          },
          "description": {
            "type": "string",
            "description": "The description of the object.",
            "example": "Water stream pipe"
          },
          "source": {
            "type": "string",
            "description": "The name of the data source containing the primary information about the object.",
            "example": "dolphin_rex_pipes",
            "pattern": "^$|.{2,128}"
          },
          "dataSetId": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the data set this node belongs to.",
            "example": 12
          },
          "labels": {
            "type": "array",
            "description": "A list of the labels associated with this node.",
            "example": [
              "resource",
              "PIPE"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 2147483647,
            "minItems": 1
          },
          "relatedResources": {
            "type": "array",
            "description": "Nodes this node is connected to, with relationship type and direction.",
            "example": [
              {
                "id": 34,
                "externalId": "sensor_abc",
                "relationshipType": "PUBLISHES_DATA_TO",
                "direction": "OUTBOUND"
              }
            ],
            "items": {
              "$ref": "#/components/schemas/RelatedNode"
            }
          },
          "unit": {
            "type": "string",
            "description": "The unit that the time series use.",
            "example": "kg/hr",
            "maxLength": 64,
            "minLength": 0
          },
          "unitExternalId": {
            "type": "string",
            "description": "The external id of the unit that the time series use.",
            "example": "mass_flow_rate_kghr",
            "maxLength": 256,
            "minLength": 3
          },
          "securityCategories": {
            "type": "array",
            "description": "A collection of security categories.",
            "example": [
              33,
              5,
              128
            ],
            "items": {
              "type": "integer",
              "format": "int32"
            }
          },
          "tableEngine": {
            "type": "string"
          },
          "valueType": {
            "type": "string",
            "description": "The value type of the time series. Can be one of BIGINT, FLOAT, FLOAT32 (default), NUMERIC, DECIMAL32, TEXT and MIXED. Choosing the right one can optimize processing speed and reduce costs.",
            "example": "FLOAT",
            "minLength": 1
          },
          "createdTime": {
            "type": "string",
            "format": "date-time",
            "description": "When the object was created, as an ISO-8601 UTC timestamp.",
            "example": "2024-06-17T12:34:56Z",
            "readOnly": true
          },
          "lastUpdatedTime": {
            "type": "string",
            "format": "date-time",
            "description": "When the object was last updated, as an ISO-8601 UTC timestamp.",
            "example": "2024-06-17T12:34:56Z",
            "readOnly": true
          }
        },
        "required": [
          "externalId",
          "labels",
          "name",
          "unit",
          "valueType"
        ]
      },
      "DuplicateError": {
        "type": "object",
        "description": "ExternalId already exists",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32"
          },
          "message": {
            "type": "string"
          },
          "duplicated": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": {
                "type": "string"
              }
            }
          }
        }
      },
      "Search Form": {
        "type": "object",
        "description": "Search Form Object",
        "properties": {
          "query": {
            "type": "string",
            "maxLength": 140,
            "minLength": 3
          }
        },
        "required": [
          "query"
        ]
      },
      "SearchBodyTimeseries Query Filter": {
        "type": "object",
        "properties": {
          "search": {
            "$ref": "#/components/schemas/Search Form"
          },
          "filter": {
            "$ref": "#/components/schemas/Timeseries Query Filter"
          },
          "limit": {
            "type": "integer",
            "format": "int32",
            "maximum": 1000
          }
        }
      },
      "TimeFilter": {
        "type": "object",
        "description": "An inclusive time window; either bound may be omitted.",
        "properties": {
          "min": {
            "type": "string",
            "format": "date-time",
            "description": "The minimum ISO 8601 time or epoch time.",
            "example": "2024-01-01T00:00Z or 1710069401321"
          },
          "max": {
            "type": "string",
            "format": "date-time",
            "description": "The maximum ISO 8601 time or epoch time.",
            "example": "2024-01-02T03:00Z or 1714461401221"
          }
        }
      },
      "Timeseries Query Filter": {
        "type": "object",
        "description": "Timeseries Query Filter Object",
        "properties": {
          "id": {
            "type": "array",
            "description": "Nodes with any of these ids.",
            "example": [
              "12",
              "18"
            ],
            "items": {
              "type": "integer",
              "format": "int64"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "externalId": {
            "type": "array",
            "description": "Nodes matching any of these external ids. `*` and `%` are wildcards; `_` is literal.",
            "example": [
              "sap_work_orders",
              "plant_*"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "name": {
            "type": "array",
            "description": "Nodes whose name matches any of these patterns. `*` and `%` are wildcards.",
            "example": [
              "SAP*",
              "Plant A"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "source": {
            "type": "array",
            "description": "Nodes whose source matches any of these patterns. `*` and `%` are wildcards.",
            "example": [
              "sap",
              "opc_*"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "labels": {
            "type": "array",
            "description": "Nodes carrying all of these labels.",
            "example": [
              "PUMP",
              "CRITICAL"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Metadata entries that must all be present. A null value matches the key alone.",
            "example": {
              "owner": "plant-a",
              "health": null
            }
          },
          "createdTime": {
            "$ref": "#/components/schemas/TimeFilter"
          },
          "lastUpdatedTime": {
            "$ref": "#/components/schemas/TimeFilter"
          },
          "dataSetId": {
            "type": "array",
            "description": "Restrict to nodes in these data sets, each given by id or externalId. A data set stands in for everything beneath it in the BELONGS_TO hierarchy, so naming a parent covers its children. Omit the field (or send null) for no data set restriction; an explicit empty list matches nothing.",
            "example": [
              {
                "id": "43"
              },
              {
                "externalId": "data_set_sap"
              }
            ],
            "items": {
              "$ref": "#/components/schemas/IdCollection"
            }
          },
          "unit": {
            "type": "array",
            "description": "Timeseries whose unit matches any of these patterns. `*` and `%` are wildcards.",
            "example": [
              "kg/hr",
              "deg_*"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "unitExternalId": {
            "type": "array",
            "description": "Timeseries whose unit external id matches any of these patterns.",
            "example": [
              "mass_flow_rate_kghr",
              "temperature_*"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "valueType": {
            "type": "array",
            "description": "Timeseries storing any of these value types.",
            "example": [
              "FLOAT",
              "BIGINT"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 100,
            "minItems": 0
          }
        }
      },
      "DataSort": {
        "type": "object",
        "properties": {
          "property": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "order": {
            "type": "string"
          },
          "nulls": {
            "type": "string",
            "pattern": "^[a-zA-Z]+$"
          }
        }
      },
      "Timeseries Query": {
        "type": "object",
        "description": "Timeseries Query Object",
        "properties": {
          "filter": {
            "$ref": "#/components/schemas/Timeseries Query Filter"
          },
          "limit": {
            "type": "integer",
            "format": "int32",
            "maximum": 10000
          },
          "sort": {
            "$ref": "#/components/schemas/DataSort"
          },
          "cursor": {
            "type": "string",
            "description": "Opaque cursor from a previous response's `nextCursor`. Must be sent with the same `sort` that produced it.",
            "maxLength": 4096,
            "minLength": 0
          }
        }
      },
      "Data Point": {
        "type": "object",
        "description": "Data Point with timestamp and value",
        "properties": {
          "timestamp": {
            "type": "string",
            "description": "The timestamp of the data point. Can be either ISO 8601 formatted \"2024-08-30T22:00:00Z\" or epoch time",
            "example": "2024-08-30T22:00:00Z or 1723759200000"
          },
          "value": {
            "description": "The data point value as a string, interpreted per the timeseries `valueType`:\n- `BIGINT` — whole number, max 8 bytes. No fractional part.\n- `FLOAT` — floating point, max 8 bytes. Fast to aggregate, but carries tiny binary rounding error (it is a float, not an exact decimal).\n- `FLOAT32` — single-precision floating point, 4 bytes (~7 significant digits). Same float rounding caveat as FLOAT; use when Float64's range/precision isn't needed.\n- `NUMERIC` — exact decimal, max 8 bytes: large magnitude range and 6 fractional digits. Use when values must be exact.\n- `DECIMAL32` — compact exact decimal, 4 bytes (Decimal32(4)): every value is rounded half-up to 4 decimal places, and a magnitude beyond ±99999.9999 is clamped to that range and logged (the data point is kept, the batch is never rejected). Use NUMERIC if larger magnitudes must be stored faithfully.\n- `TEXT` — arbitrary string (states, labels, modes, or non-numeric readings).\n- `MIXED` — a number or text per point, for sensors that emit both (e.g. readings plus a `FAULT` status). Numbers are aggregated; text rows are skipped by aggregates.",
            "example": 344.544
          }
        },
        "required": [
          "timestamp",
          "value"
        ]
      },
      "DatapointCollection": {
        "type": "object",
        "description": "Object with timeseries reference and a collection of data points.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The time series id.",
            "example": 232345167
          },
          "externalId": {
            "type": "string",
            "description": "The time series external id.",
            "example": "a4545_well_pump_pressure_a"
          },
          "datapoints": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Data Point"
            }
          },
          "nextCursor": {
            "type": "string",
            "description": "If more than 100 000 datapoints are returned, a cursor hash value will be returned",
            "example": "0195cd12-7cc7-74af-9ada-67dc22f428f6"
          },
          "unit": {
            "type": "string",
            "description": "Time series data unit.",
            "example": "Celsius"
          },
          "unitExternalId": {
            "type": "string",
            "description": "Time series external unit id.",
            "example": "volume_barrel_pet_us"
          }
        }
      },
      "DatapointsCollectionDataWrapper": {
        "type": "object",
        "description": "Add either id or external id or a mix of both to the request body and the datapoints you want to insert",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DatapointCollection"
            }
          }
        }
      },
      "Data Retriever Filter": {
        "type": "object",
        "description": "Retrieves a list of data points from multiple time series in a project.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the time series object. You need to supply either id or externalId.",
            "example": 5677892
          },
          "start": {
            "type": "string",
            "description": "Get datapoints starting from, and including, this time.",
            "example": "2020-01-01T01:00Z"
          },
          "end": {
            "type": "string",
            "description": "Get datapoints up to, but excluding, this point in time.",
            "example": "2020-01-01T02:00Z"
          },
          "limit": {
            "type": "integer",
            "format": "int32",
            "description": "The maximum number of datapoints to return. The maximum is 100000.",
            "example": 100,
            "maximum": 100000,
            "minimum": 0
          },
          "aggregates": {
            "type": "array",
            "description": "The list of aggregations to apply to the data points.",
            "example": [
              "sum",
              "min",
              "max"
            ],
            "items": {
              "type": "string"
            }
          },
          "granularity": {
            "type": "string",
            "description": "The time granularity of the data points.",
            "example": "minute"
          },
          "includeOutsidePoints": {
            "type": "boolean"
          },
          "cursor": {
            "type": "string",
            "description": "The cursor to use for pagination. The cursor is returned in the response."
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the time series. You need to supply either id or externalId.",
            "example": "logistics_sap_orders_23"
          },
          "mergeDuplicates": {
            "type": "boolean",
            "description": "If you have recently updated the data you have queried, enable this to handle data merging."
          }
        },
        "required": [
          "externalId",
          "id"
        ]
      },
      "DataRetrieverForDatapoints": {
        "type": "object",
        "description": "Retrieves a list of data points from multiple time series in a project.",
        "properties": {
          "items": {
            "type": "array",
            "description": "A list of DataRetrieverFilter objects for filtering data points.",
            "items": {
              "$ref": "#/components/schemas/Data Retriever Filter"
            }
          },
          "start": {
            "type": "string",
            "description": "Get datapoints starting from, and including, this time.",
            "example": "2020-01-01T01:00Z"
          },
          "end": {
            "type": "string",
            "description": "Get datapoints up to, but excluding, this point in time.",
            "example": "2020-01-01T02:00Z"
          },
          "limit": {
            "type": "integer",
            "format": "int32",
            "description": "The maximum number of datapoints to return. The maximum is 100000.",
            "example": 100,
            "maximum": 100000,
            "minimum": 0
          },
          "aggregates": {
            "type": "array",
            "description": "The list of aggregations to apply to the data points.",
            "example": [
              "sum",
              "min",
              "max"
            ],
            "items": {
              "type": "string"
            }
          },
          "granularity": {
            "type": "string",
            "description": "The time granularity of the data points.",
            "example": "minute"
          },
          "includeOutsidePoints": {
            "type": "boolean"
          },
          "ignoreUnknownIds": {
            "type": "boolean"
          }
        }
      },
      "DatapointsDataWrapper": {
        "type": "object",
        "description": "Times series with id and external id and the datapoints.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DatapointCollection"
            }
          }
        }
      },
      "Data point": {
        "type": "object",
        "description": "Data point with timestamp and value",
        "properties": {
          "timestamp": {
            "type": "integer",
            "format": "int64"
          },
          "isoTime": {
            "type": "string",
            "format": "date-time"
          },
          "value": {
            "type": "string"
          }
        }
      },
      "Timeseries Collection": {
        "type": "object",
        "description": "Timeseries with data points collection",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Timeseries With Datapoints"
            }
          }
        }
      },
      "Timeseries With Datapoints": {
        "type": "object",
        "description": "Timeseries with datapoints",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the time series object.",
            "example": 5677892
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the time series.",
            "example": "logistics_sap_orders_11"
          },
          "datapoints": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Data point"
            }
          }
        }
      },
      "DataWrapper": {
        "type": "object",
        "description": "DataWrapper with items",
        "properties": {
          "items": {
            "type": "array",
            "items": {}
          },
          "warnings": {
            "type": "array",
            "description": "Policy violations that were allowed through and recorded for review. Absent when there are none.",
            "items": {
              "$ref": "#/components/schemas/PolicyWarning"
            }
          },
          "nextCursor": {
            "type": "string",
            "description": "Opaque cursor for the next page. Send it back as `cursor`. Absent when there are no further pages."
          }
        }
      },
      "PolicyWarning": {
        "type": "object",
        "description": "A policy violation that was allowed through and recorded for review.",
        "properties": {
          "index": {
            "type": "integer",
            "format": "int32",
            "description": "Position of the offending item in the submitted batch.",
            "example": 3
          },
          "externalId": {
            "type": "string",
            "description": "The external id that triggered the warning, exactly as submitted.",
            "example": "Pump-A-01"
          },
          "policy": {
            "type": "string",
            "description": "External id of the policy that fired.",
            "example": "naming_snake_case"
          },
          "message": {
            "type": "string",
            "description": "What is wrong.",
            "example": "Does not match naming policy 'snake_case'."
          },
          "suggestion": {
            "type": "string",
            "description": "A conforming alternative, where one can be derived. Not applied — external ids are stored exactly as sent.",
            "example": "pump_a_01"
          }
        }
      },
      "SubscriptionFilter": {
        "type": "object",
        "description": "Filter criteria for listing subscriptions.",
        "properties": {
          "timeseries": {
            "type": "array",
            "description": "Return only subscriptions bound to these timeseries. Each entry can specify a timeseries id, external id, or both. Empty means no timeseries filter.",
            "example": [
              {
                "id": 29
              },
              {
                "externalId": "heater_2012_temp"
              }
            ],
            "items": {
              "$ref": "#/components/schemas/IdCollection"
            }
          }
        }
      },
      "SubscriptionRetriever": {
        "type": "object",
        "description": "Configure how you want to fetch subscriptions.",
        "properties": {
          "filter": {
            "$ref": "#/components/schemas/SubscriptionFilter"
          },
          "limit": {
            "type": "integer",
            "format": "int32",
            "default": 100,
            "description": "Maximum number of subscriptions to return.",
            "example": 100,
            "maximum": 10000
          },
          "sort": {
            "$ref": "#/components/schemas/DataSort"
          },
          "includeSystemManaged": {
            "type": "boolean",
            "default": false,
            "description": "Include subscriptions auto-provisioned by the function-binding lifecycle (system_managed=true). Default false hides them from user-facing listings; set to true from internal callers (e.g. function workers) that need to discover their bindings.",
            "example": false
          }
        }
      },
      "Subscription": {
        "type": "object",
        "description": "A subscription bound to one or more timeseries. Creates a Pulsar topic and subscription on persist.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the subscription.",
            "example": 12345
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the subscription. Also used as the Pulsar subscription name.",
            "example": "boiler_room_readings_sub",
            "maxLength": 256,
            "minLength": 3
          },
          "name": {
            "type": "string",
            "description": "The display name of the subscription.",
            "example": "Boiler Room Readings",
            "maxLength": 256,
            "minLength": 3
          },
          "timeseries": {
            "type": "array",
            "description": "The timeseries this subscription is bound to. Each entry can specify a timeseries id, external id, or both. At least one entry is required.",
            "example": [
              {
                "id": 29
              },
              {
                "externalId": "heater_2012_temp"
              }
            ],
            "items": {
              "$ref": "#/components/schemas/IdCollection"
            },
            "minItems": 1
          },
          "systemManaged": {
            "type": "boolean",
            "description": "True when this subscription was auto-provisioned by the function-binding lifecycle. Server-controlled — read-only on the wire. System-managed subscriptions are hidden from /subscriptions/list by default and refuse manual deletes.",
            "readOnly": true
          },
          "dateCreated": {
            "type": "string",
            "format": "date-time",
            "description": "When the subscription was created. Server-assigned on create."
          },
          "lastUpdated": {
            "type": "string",
            "format": "date-time",
            "description": "When the subscription was last updated. Server-assigned."
          }
        },
        "required": [
          "externalId",
          "name",
          "timeseries"
        ]
      },
      "Subscription Collection": {
        "type": "object",
        "description": "Data Response with Subscriptions as collection.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Subscription"
            }
          }
        }
      },
      "GeoLocation": {
        "type": "object",
        "properties": {
          "json": {
            "type": "string"
          }
        }
      },
      "Graph Data Nodes and Edges": {
        "type": "object",
        "description": "Graph Data Nodes and Edges Object",
        "properties": {
          "nodes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UpdateResourceForm"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "relations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UpdateRelForm"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "warnings": {
            "type": "array",
            "description": "Policy violations that were allowed through and recorded for review. Absent when there are none.",
            "items": {
              "$ref": "#/components/schemas/PolicyWarning"
            }
          }
        }
      },
      "RelFields": {
        "type": "object",
        "properties": {
          "start": {
            "$ref": "#/components/schemas/UpdateNumberField"
          },
          "end": {
            "$ref": "#/components/schemas/UpdateNumberField"
          },
          "fromExternalId": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "toExternalId": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "relationship": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "relationshipId": {
            "$ref": "#/components/schemas/UpdateNumberField"
          },
          "description": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "metadata": {
            "$ref": "#/components/schemas/UpdateMapField"
          }
        }
      },
      "ResourceFields": {
        "type": "object",
        "properties": {
          "externalId": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "name": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "description": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "dataSetId": {
            "$ref": "#/components/schemas/UpdateNumberField"
          },
          "metadata": {
            "$ref": "#/components/schemas/UpdateMapField"
          },
          "source": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "labels": {
            "$ref": "#/components/schemas/UpdateListField"
          },
          "geoLocation": {
            "$ref": "#/components/schemas/UpdateGeoLocationField"
          }
        }
      },
      "UpdateGeoLocationField": {
        "type": "object",
        "description": "What GeoJSON geometry you want the geolocation set to",
        "properties": {
          "set": {
            "$ref": "#/components/schemas/GeoLocation",
            "description": "The GeoJSON geometry to set"
          },
          "setNull": {
            "type": "boolean",
            "description": "When you want the value to be null",
            "example": true
          }
        }
      },
      "UpdateListField": {
        "type": "object",
        "description": "What values you want to set the list to",
        "properties": {
          "set": {
            "type": "array",
            "description": "What values you want in the list. This will remove all existing entries.",
            "example": [
              "topic",
              "message"
            ],
            "items": {
              "type": "string"
            }
          },
          "add": {
            "type": "array",
            "description": "What values you want to add to the list. This will keep all existing entries.",
            "example": [
              "topic",
              "message"
            ],
            "items": {
              "type": "string"
            }
          },
          "remove": {
            "type": "array",
            "description": "What values you want to remove from the list.",
            "example": [
              "topic",
              "message"
            ],
            "items": {
              "type": "string"
            }
          }
        }
      },
      "UpdateRelForm": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "update": {
            "$ref": "#/components/schemas/RelFields"
          }
        }
      },
      "UpdateResourceForm": {
        "type": "object",
        "properties": {
          "externalId": {
            "type": "string",
            "pattern": "[A-Za-z0-9._:+=-]+"
          },
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "update": {
            "$ref": "#/components/schemas/ResourceFields"
          }
        }
      },
      "GraphResources": {
        "type": "object",
        "description": "Resources and Relations for graph data network.",
        "properties": {
          "nodes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Resource"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "relations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Relation"
            },
            "maxItems": 1000,
            "minItems": 0
          }
        }
      },
      "Relation": {
        "type": "object",
        "description": "Relation object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "start": {
            "type": "integer",
            "format": "int64"
          },
          "end": {
            "type": "integer",
            "format": "int64"
          },
          "type": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "relationshipTypeId": {
            "type": "integer",
            "format": "int64"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "Resource": {
        "type": "object",
        "description": "Resource description",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the object.",
            "example": 5677892
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the object.",
            "example": "klp_pipe_ws_a1212_dl",
            "maxLength": 256,
            "minLength": 3
          },
          "name": {
            "type": "string",
            "description": "The name of the object.",
            "example": "klp pipe ws-a1212-dl",
            "maxLength": 512,
            "minLength": 3
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Entity specific metadata. A key-value store.",
            "example": {
              "work_order": "wo-sap-12344"
            }
          },
          "description": {
            "type": "string",
            "description": "The description of the object.",
            "example": "Water stream pipe"
          },
          "source": {
            "type": "string",
            "description": "The name of the data source containing the primary information about the object.",
            "example": "dolphin_rex_pipes",
            "pattern": "^$|.{2,128}"
          },
          "dataSetId": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the data set this node belongs to.",
            "example": 12
          },
          "labels": {
            "type": "array",
            "description": "A list of the labels associated with this node.",
            "example": [
              "resource",
              "PIPE"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 2147483647,
            "minItems": 1
          },
          "relatedResources": {
            "type": "array",
            "description": "Nodes this node is connected to, with relationship type and direction.",
            "example": [
              {
                "id": 34,
                "externalId": "sensor_abc",
                "relationshipType": "PUBLISHES_DATA_TO",
                "direction": "OUTBOUND"
              }
            ],
            "items": {
              "$ref": "#/components/schemas/RelatedNode"
            }
          },
          "isRoot": {
            "type": "boolean",
            "description": "Is this a root resource?",
            "example": true
          },
          "geoLocation": {
            "$ref": "#/components/schemas/GeoLocation"
          },
          "createdTime": {
            "type": "string",
            "format": "date-time",
            "description": "When the object was created, as an ISO-8601 UTC timestamp.",
            "example": "2024-06-17T12:34:56Z",
            "readOnly": true
          },
          "lastUpdatedTime": {
            "type": "string",
            "format": "date-time",
            "description": "When the object was last updated, as an ISO-8601 UTC timestamp.",
            "example": "2024-06-17T12:34:56Z",
            "readOnly": true
          }
        },
        "required": [
          "externalId",
          "labels",
          "name"
        ]
      },
      "Resource Query Filter": {
        "type": "object",
        "description": "Resource Query Filter Object",
        "properties": {
          "id": {
            "type": "array",
            "description": "Nodes with any of these ids.",
            "example": [
              "12",
              "18"
            ],
            "items": {
              "type": "integer",
              "format": "int64"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "externalId": {
            "type": "array",
            "description": "Nodes matching any of these external ids. `*` and `%` are wildcards; `_` is literal.",
            "example": [
              "sap_work_orders",
              "plant_*"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "name": {
            "type": "array",
            "description": "Nodes whose name matches any of these patterns. `*` and `%` are wildcards.",
            "example": [
              "SAP*",
              "Plant A"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "source": {
            "type": "array",
            "description": "Nodes whose source matches any of these patterns. `*` and `%` are wildcards.",
            "example": [
              "sap",
              "opc_*"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "labels": {
            "type": "array",
            "description": "Nodes carrying all of these labels.",
            "example": [
              "PUMP",
              "CRITICAL"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Metadata entries that must all be present. A null value matches the key alone.",
            "example": {
              "owner": "plant-a",
              "health": null
            }
          },
          "createdTime": {
            "$ref": "#/components/schemas/TimeFilter"
          },
          "lastUpdatedTime": {
            "$ref": "#/components/schemas/TimeFilter"
          },
          "dataSetId": {
            "type": "array",
            "description": "Restrict to nodes in these data sets, each given by id or externalId. A data set stands in for everything beneath it in the BELONGS_TO hierarchy, so naming a parent covers its children. Omit the field (or send null) for no data set restriction; an explicit empty list matches nothing.",
            "example": [
              {
                "id": "43"
              },
              {
                "externalId": "data_set_sap"
              }
            ],
            "items": {
              "$ref": "#/components/schemas/IdCollection"
            }
          },
          "nodeType": {
            "type": "array",
            "description": "Restrict to these node types. Omit for every type.",
            "example": [
              "resource",
              "timeseries"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 20,
            "minItems": 0
          },
          "isRoot": {
            "type": "boolean",
            "description": "Restrict to root nodes, or to non-root ones.",
            "example": true
          }
        }
      },
      "SearchBodyResource Query Filter": {
        "type": "object",
        "properties": {
          "search": {
            "$ref": "#/components/schemas/Search Form"
          },
          "filter": {
            "$ref": "#/components/schemas/Resource Query Filter"
          },
          "limit": {
            "type": "integer",
            "format": "int32",
            "maximum": 1000
          }
        }
      },
      "Resource Collection": {
        "type": "object",
        "description": "Data Response with Resource collection.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Resource"
            }
          }
        }
      },
      "Resource Query": {
        "type": "object",
        "description": "Resource Query Object",
        "properties": {
          "filter": {
            "$ref": "#/components/schemas/Resource Query Filter"
          },
          "limit": {
            "type": "integer",
            "format": "int32",
            "maximum": 10000
          },
          "sort": {
            "$ref": "#/components/schemas/DataSort"
          },
          "cursor": {
            "type": "string",
            "description": "Opaque cursor from a previous response's `nextCursor`. Must be sent with the same `sort` that produced it.",
            "maxLength": 4096,
            "minLength": 0
          }
        }
      },
      "RelatedResourcesForm": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "externalId": {
            "type": "string"
          },
          "depth": {
            "type": "integer",
            "format": "int32"
          },
          "relationshipTypes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "limit": {
            "type": "integer",
            "format": "int32"
          },
          "excludedLabels": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ResourceNetwork": {
        "type": "object",
        "properties": {
          "nodes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Resource"
            },
            "uniqueItems": true
          },
          "edges": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Relation"
            },
            "uniqueItems": true
          },
          "labels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Label"
            },
            "uniqueItems": true
          }
        }
      },
      "FetchNearestResourcesForm": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "externalId": {
            "type": "string"
          },
          "endLabels": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "limit": {
            "type": "integer",
            "format": "int32"
          },
          "relationshipTypes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "excludedLabels": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "CreateResourceForm": {
        "type": "object",
        "description": "Describes how to create resources.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "externalId": {
            "type": "string",
            "maxLength": 256,
            "minLength": 3
          },
          "isRoot": {
            "type": "boolean"
          },
          "name": {
            "type": "string",
            "maxLength": 512,
            "minLength": 3
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "description": {
            "type": "string"
          },
          "dataSetId": {
            "type": "integer",
            "format": "int64"
          },
          "createdTime": {
            "type": "string",
            "format": "date-time"
          },
          "lastUpdatedTime": {
            "type": "string",
            "format": "date-time"
          },
          "source": {
            "type": "string",
            "pattern": "^$|.{2,128}"
          },
          "labels": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 2147483647,
            "minItems": 1
          },
          "geoLocation": {
            "$ref": "#/components/schemas/GeoLocation"
          }
        },
        "required": [
          "externalId",
          "name"
        ]
      },
      "CreateResources": {
        "type": "object",
        "description": "CreateResources for graph data network.",
        "properties": {
          "nodes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CreateResourceForm"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "relations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Relationship"
            },
            "maxItems": 1000,
            "minItems": 0
          }
        }
      },
      "Relationship": {
        "type": "object",
        "description": "Relationship between two resources.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "fromExternalId": {
            "type": "string"
          },
          "toExternalId": {
            "type": "string"
          },
          "fromId": {
            "type": "integer",
            "format": "int64"
          },
          "toId": {
            "type": "integer",
            "format": "int64"
          },
          "relationshipType": {
            "type": "string"
          },
          "relationshipTypeId": {
            "type": "integer",
            "format": "int64"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "dataSetId": {
            "type": "integer",
            "format": "int64"
          },
          "description": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "writeOnly": true
          }
        }
      },
      "Policy Update Form": {
        "type": "object",
        "description": "Policy Update Form Object",
        "properties": {
          "name": {
            "$ref": "#/components/schemas/UpdateStringField",
            "description": "Replace the display name. Required on the policy, so `setNull` is not honoured."
          },
          "externalId": {
            "$ref": "#/components/schemas/UpdateStringField",
            "description": "Replace the external id; it is normalised and its hash re-derived. Identity key, so `setNull` is not honoured."
          },
          "description": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "source": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "metadata": {
            "$ref": "#/components/schemas/UpdateMapField"
          },
          "deactivated": {
            "$ref": "#/components/schemas/UpdateBooleanField"
          },
          "templateId": {
            "$ref": "#/components/schemas/UpdateNumberField",
            "description": "Apply a governance template, merging its metadata into the policy's. `setNull` is not honoured — drop the template with `metadata.remove: [\"templateId\"]`."
          }
        }
      },
      "Update Policy Collection": {
        "type": "object",
        "description": "Request with a collection of policy updates.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Update Policy Form"
            }
          }
        }
      },
      "Update Policy Form": {
        "type": "object",
        "description": "Update Policy Form. One of the id or externalId fields is required.",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the policy to update. Supply this or externalId.",
            "example": 5
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the policy to update. Supply this or id.",
            "example": "policy_is_write_protected"
          },
          "update": {
            "$ref": "#/components/schemas/Policy Update Form"
          }
        }
      },
      "UpdateBooleanField": {
        "type": "object",
        "description": "What value you want the boolean field set to",
        "properties": {
          "set": {
            "type": "boolean",
            "description": "What value you want the boolean field set to. Omit the field entirely to leave it unchanged.",
            "example": true
          }
        }
      },
      "NamingCheckForm": {
        "type": "object",
        "description": "Candidate external ids to check against the naming policy.",
        "properties": {
          "externalIds": {
            "type": "array",
            "description": "External ids to check. Nothing is written.",
            "example": [
              "COM-99-PT-1034",
              "pump-a-01"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "names": {
            "type": "array",
            "description": "Optional names for the external ids above, aligned by position. Used to derive a more meaningful suggestion. Either omit entirely or supply exactly as many as there are external ids.",
            "example": [
              "Valve 21 PT 1034",
              "Pump A 01"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "dataSetId": {
            "type": "integer",
            "format": "int64",
            "description": "Check against the policy governing this data set. Omit for the tenant policy.",
            "example": 12
          }
        },
        "required": [
          "externalIds"
        ]
      },
      "ProblemDetail": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer",
            "format": "int32"
          },
          "detail": {
            "type": "string"
          },
          "instance": {
            "type": "string",
            "format": "uri"
          },
          "properties": {
            "type": "object",
            "additionalProperties": {}
          }
        }
      },
      "PolicyFinding": {
        "type": "object",
        "properties": {
          "index": {
            "type": "integer",
            "format": "int32"
          },
          "externalId": {
            "type": "string"
          },
          "decision": {
            "type": "string",
            "enum": [
              "OK",
              "WARNING",
              "NOT_OK"
            ]
          },
          "policy": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "suggestion": {
            "type": "string"
          },
          "rejection": {
            "type": "boolean"
          },
          "warning": {
            "type": "boolean"
          }
        }
      },
      "Function": {
        "type": "object",
        "description": "Function datastore node",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the object.",
            "example": 5677892
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the object.",
            "example": "klp_pipe_ws_a1212_dl",
            "maxLength": 256,
            "minLength": 3
          },
          "name": {
            "type": "string",
            "description": "The name of the object.",
            "example": "klp pipe ws-a1212-dl",
            "maxLength": 512,
            "minLength": 3
          },
          "labels": {
            "type": "array",
            "description": "A list of the labels associated with this node.",
            "example": [
              "resource",
              "PIPE"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 2147483647,
            "minItems": 1
          },
          "createdTime": {
            "type": "string",
            "format": "date-time",
            "description": "When the object was created, as an ISO-8601 UTC timestamp.",
            "example": "2024-06-17T12:34:56Z",
            "readOnly": true
          },
          "lastUpdatedTime": {
            "type": "string",
            "format": "date-time",
            "description": "When the object was last updated, as an ISO-8601 UTC timestamp.",
            "example": "2024-06-17T12:34:56Z",
            "readOnly": true
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Entity specific metadata. A key-value store.",
            "example": {
              "work_order": "wo-sap-12344"
            }
          },
          "description": {
            "type": "string",
            "description": "The description of the object.",
            "example": "Water stream pipe"
          },
          "source": {
            "type": "string",
            "description": "The name of the data source containing the primary information about the object.",
            "example": "dolphin_rex_pipes",
            "pattern": "^$|.{2,128}"
          },
          "dataSetId": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the data set this node belongs to.",
            "example": 12
          },
          "relatedResources": {
            "type": "array",
            "description": "Nodes this node is connected to, with relationship type and direction.",
            "example": [
              {
                "id": 34,
                "externalId": "sensor_abc",
                "relationshipType": "PUBLISHES_DATA_TO",
                "direction": "OUTBOUND"
              }
            ],
            "items": {
              "$ref": "#/components/schemas/RelatedNode"
            }
          }
        },
        "required": [
          "externalId",
          "labels",
          "name"
        ]
      },
      "Function Collection": {
        "type": "object",
        "description": "Data response with a collection of functions.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Function"
            }
          }
        }
      },
      "Update Event Collection": {
        "type": "object",
        "description": "Data Response with Events as collection.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Update Event Form"
            }
          }
        }
      },
      "Update Event Fields": {
        "type": "object",
        "description": "Update Event Fields",
        "properties": {
          "externalId": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "description": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "type": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "subType": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "status": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "dataSetId": {
            "$ref": "#/components/schemas/UpdateNumberField"
          },
          "metadata": {
            "$ref": "#/components/schemas/UpdateMapField"
          },
          "source": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "relatedResources": {
            "$ref": "#/components/schemas/UpdateIdCollectionListField"
          }
        }
      },
      "Update Event Form": {
        "type": "object",
        "description": "Update Event Form. One of external id or id fields are required.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the event as uuid.",
            "example": "db02f65e-3b77-4d5e-a8f6-4a2b5d2c8f19"
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the event.",
            "example": "work_order_sap_chemicals",
            "maxLength": 256,
            "minLength": 3,
            "pattern": "[A-Za-z0-9._:+=-]+"
          },
          "update": {
            "$ref": "#/components/schemas/Update Event Fields"
          }
        },
        "required": [
          "externalId",
          "id"
        ]
      },
      "UpdateIdCollectionListField": {
        "type": "object",
        "description": "What entries you want in the list. Each entry may carry id, externalId, or both.",
        "properties": {
          "set": {
            "type": "array",
            "description": "What entries you want in the list. This will remove all existing entries.",
            "example": [
              {
                "externalId": "work_order_sap_1234"
              }
            ],
            "items": {
              "$ref": "#/components/schemas/IdCollection"
            }
          },
          "add": {
            "type": "array",
            "description": "What entries you want to add to the list. This will keep all existing entries.",
            "example": [
              {
                "id": 22
              }
            ],
            "items": {
              "$ref": "#/components/schemas/IdCollection"
            }
          },
          "remove": {
            "type": "array",
            "description": "What entries you want to remove from the list. An entry matches on either id or externalId.",
            "example": [
              {
                "id": 22
              }
            ],
            "items": {
              "$ref": "#/components/schemas/IdCollection"
            }
          }
        }
      },
      "Event": {
        "type": "object",
        "description": "Event description",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id of the event.",
            "example": "5677892"
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the event.",
            "example": "work_order_sap_chemicals",
            "maxLength": 256,
            "minLength": 3
          },
          "type": {
            "type": "string",
            "description": "Type of the event",
            "example": "Alarm",
            "maxLength": 128,
            "minLength": 3
          },
          "subType": {
            "type": "string",
            "description": "Sub-type of the event",
            "example": "Electrical",
            "maxLength": 128,
            "minLength": 3
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Event metadata, additional fields you can bind information with.",
            "example": {
              "definition": "SAP ORDER PLACED"
            }
          },
          "description": {
            "type": "string",
            "description": "The description of the event.",
            "example": "This event was caused by...."
          },
          "status": {
            "type": "string",
            "description": "Status of the event",
            "example": "Status.COMPLETE or FAILED",
            "maxLength": 128,
            "minLength": 3
          },
          "dataSetId": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the data set for this event. Optional.",
            "example": 2323
          },
          "relatedResources": {
            "type": "array",
            "description": "Resources that this event has a relation to. Supply id, externalId or both; both are returned.",
            "example": [
              {
                "id": 34,
                "externalId": "sensor_abc"
              }
            ],
            "items": {
              "$ref": "#/components/schemas/IdCollection"
            }
          },
          "source": {
            "type": "string",
            "description": "The source for this event.",
            "example": "SAP",
            "maxLength": 128,
            "minLength": 2
          },
          "createdTime": {
            "type": "string",
            "format": "date-time",
            "description": "When the object was created, as an ISO-8601 UTC timestamp.",
            "example": "2024-06-17T12:34:56Z",
            "readOnly": true
          },
          "lastUpdatedTime": {
            "type": "string",
            "format": "date-time",
            "description": "When the object was last updated, as an ISO-8601 UTC timestamp.",
            "example": "2024-06-17T12:34:56Z",
            "readOnly": true
          },
          "eventTime": {
            "type": "string",
            "format": "date-time",
            "description": "The event time for this event. On output this is an ISO-8601 string; on input, either epoch millis [UTC] or ISO-8601 is accepted.",
            "example": "2024-08-30T22:00:00Z"
          }
        },
        "required": [
          "eventTime",
          "externalId",
          "type"
        ]
      },
      "Event Collection": {
        "type": "object",
        "description": "Data Response with Events as collection.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Event"
            }
          }
        }
      },
      "EventFilter": {
        "type": "object",
        "description": "Configure event filter.",
        "properties": {
          "externalId": {
            "type": "array",
            "description": "Events matching any of these external ids. `*` and `%` are wildcards; `_` is literal.",
            "example": [
              "work_order_1234",
              "work_order_*"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "source": {
            "type": "array",
            "description": "Events whose source matches any of these patterns. `*` and `%` are wildcards.",
            "example": [
              "SAP",
              "opc_*"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "type": {
            "type": "array",
            "description": "Events matching any of these types. `*` and `%` are wildcards.",
            "example": [
              "Alarm",
              "Warning"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "subType": {
            "type": "array",
            "description": "Events matching any of these sub-types. `*` and `%` are wildcards.",
            "example": [
              "Electrical"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "status": {
            "type": "array",
            "description": "Events matching any of these statuses. `*` and `%` are wildcards.",
            "example": [
              "OPEN",
              "IN_PROGRESS"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "dataSetId": {
            "type": "array",
            "description": "Restrict to events in these data sets, each given by id or externalId — the same reference shape as relatedResources below. A data set stands in for everything beneath it in the BELONGS_TO hierarchy, so naming a parent covers its children; an externalId naming no data set contributes nothing. Omit the field (or send null) for no data set restriction; an explicit empty list matches nothing.",
            "example": [
              {
                "id": "43"
              },
              {
                "externalId": "data_set_sap"
              }
            ],
            "items": {
              "$ref": "#/components/schemas/IdCollection"
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Metadata entries that must all be present. A null value matches the key alone.",
            "example": {
              "health": "good",
              "size": null
            }
          },
          "relatedResources": {
            "type": "array",
            "description": "The event must be related to ALL of these resources. Each entry may carry an id, an externalId, or both.",
            "example": [
              {
                "id": 22,
                "externalId": "work_order_sap_1234"
              }
            ],
            "items": {
              "$ref": "#/components/schemas/IdCollection"
            }
          },
          "createdTime": {
            "$ref": "#/components/schemas/TimeFilter"
          },
          "lastUpdatedTime": {
            "$ref": "#/components/schemas/TimeFilter"
          },
          "eventTime": {
            "$ref": "#/components/schemas/TimeFilter"
          }
        }
      },
      "SearchBodyEventFilter": {
        "type": "object",
        "properties": {
          "search": {
            "$ref": "#/components/schemas/Search Form"
          },
          "filter": {
            "$ref": "#/components/schemas/EventFilter"
          },
          "limit": {
            "type": "integer",
            "format": "int32",
            "maximum": 1000
          }
        }
      },
      "AdvancedFilter": {
        "type": "object",
        "description": "The advanced filter feature allows you to build more advanced filtering expressions by combining operations like equals and exists using the Boolean operators AND, OR, and NOT.",
        "properties": {
          "not": {
            "$ref": "#/components/schemas/AdvancedNotFilter"
          },
          "and": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdvancedFilter"
            }
          },
          "or": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdvancedFilter"
            }
          },
          "property": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "value": {
            "type": "string"
          },
          "values": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "in": {
            "$ref": "#/components/schemas/AdvancedFilterOperator",
            "writeOnly": true
          },
          "prefix": {
            "$ref": "#/components/schemas/AdvancedFilterOperator",
            "writeOnly": true
          },
          "equals": {
            "$ref": "#/components/schemas/AdvancedFilterOperator",
            "writeOnly": true
          }
        }
      },
      "AdvancedFilterOperator": {
        "type": "object",
        "properties": {
          "operator": {
            "type": "string",
            "enum": [
              "containsAll",
              "containsAny",
              "equals",
              "exists",
              "in",
              "prefix"
            ]
          },
          "property": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "value": {
            "type": "string"
          },
          "values": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "AdvancedNotFilter": {
        "type": "object",
        "description": "The advanced not filter feature allows you to combine NOT operators.",
        "properties": {
          "and": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdvancedFilter"
            }
          },
          "or": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdvancedFilter"
            }
          },
          "property": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "value": {
            "type": "string"
          },
          "values": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "in": {
            "$ref": "#/components/schemas/AdvancedFilterOperator",
            "writeOnly": true
          },
          "prefix": {
            "$ref": "#/components/schemas/AdvancedFilterOperator",
            "writeOnly": true
          },
          "equals": {
            "$ref": "#/components/schemas/AdvancedFilterOperator",
            "writeOnly": true
          }
        }
      },
      "EventRetreiver": {
        "type": "object",
        "description": "Configure how you want to fetch events.",
        "properties": {
          "filter": {
            "$ref": "#/components/schemas/EventFilter"
          },
          "limit": {
            "type": "integer",
            "format": "int32",
            "maximum": 10000
          },
          "cursor": {
            "type": "string",
            "description": "Where the previous page stopped: `<eventTime epoch millis>_<event id>`, from the last event of that page. Omit to start from the beginning. Fixes the order to eventTime then id ascending.",
            "example": "1754476522104_0195f3a2-4c1b-7f9e-9c3a-1b2d4e6f8a90",
            "maxLength": 4096,
            "minLength": 0
          },
          "sort": {
            "$ref": "#/components/schemas/DataSort"
          },
          "advancedFilter": {
            "$ref": "#/components/schemas/AdvancedFilter"
          }
        }
      },
      "RelTypeForm": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "name": {
            "type": "string",
            "maxLength": 128,
            "minLength": 0
          },
          "i18nCode": {
            "type": "string"
          },
          "description": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ]
      },
      "Relationship Type Form Collection": {
        "type": "object",
        "description": "Request body with the relationship types to create.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RelTypeForm"
            }
          }
        }
      },
      "Relationship Type Collection": {
        "type": "object",
        "description": "Data response with a collection of relationship types.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RelationshipType"
            }
          }
        }
      },
      "RelationshipType": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "i18nCode": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ]
      },
      "Relationship Form Collection": {
        "type": "object",
        "description": "Request body with the relationships (edges) to create.",
        "properties": {
          "items": {
            "type": "array",
            "description": "Identify each endpoint by id or externalId. Both resources must already exist.",
            "items": {
              "$ref": "#/components/schemas/Relationship"
            }
          }
        }
      },
      "Relationship Collection": {
        "type": "object",
        "description": "Data response with a collection of relationships (edges).",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Relation"
            }
          }
        }
      },
      "Data Set Form": {
        "type": "object",
        "description": "Data Set Form Object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "externalId": {
            "type": "string",
            "pattern": "[A-Za-z0-9._:+=-]+"
          },
          "update": {
            "$ref": "#/components/schemas/Data Set Update Form"
          }
        }
      },
      "Data Set Update Form": {
        "type": "object",
        "description": "Data Set Update Form Object",
        "properties": {
          "name": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "metadata": {
            "$ref": "#/components/schemas/UpdateMapField"
          },
          "description": {
            "$ref": "#/components/schemas/UpdateStringField"
          },
          "labels": {
            "$ref": "#/components/schemas/UpdateListField"
          },
          "externalId": {
            "$ref": "#/components/schemas/UpdateStringField"
          }
        }
      },
      "DataSetFormDataWrapper": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Data Set Form"
            }
          }
        }
      },
      "Data Set": {
        "type": "object",
        "description": "Data Set object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the object.",
            "example": 5677892
          },
          "externalId": {
            "type": "string",
            "description": "The external id of the object.",
            "example": "klp_pipe_ws_a1212_dl",
            "maxLength": 256,
            "minLength": 3
          },
          "name": {
            "type": "string",
            "description": "The name of the object.",
            "example": "klp pipe ws-a1212-dl",
            "maxLength": 512,
            "minLength": 3
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Entity specific metadata. A key-value store.",
            "example": {
              "work_order": "wo-sap-12344"
            }
          },
          "description": {
            "type": "string",
            "description": "The description of the object.",
            "example": "Water stream pipe"
          },
          "source": {
            "type": "string",
            "description": "The name of the data source containing the primary information about the object.",
            "example": "dolphin_rex_pipes",
            "pattern": "^$|.{2,128}"
          },
          "dataSetId": {
            "type": "integer",
            "format": "int64",
            "description": "The id of the data set this node belongs to.",
            "example": 12
          },
          "labels": {
            "type": "array",
            "description": "A list of the labels associated with this node.",
            "example": [
              "resource",
              "PIPE"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 2147483647,
            "minItems": 1
          },
          "relatedResources": {
            "type": "array",
            "description": "Nodes this node is connected to, with relationship type and direction.",
            "example": [
              {
                "id": 34,
                "externalId": "sensor_abc",
                "relationshipType": "PUBLISHES_DATA_TO",
                "direction": "OUTBOUND"
              }
            ],
            "items": {
              "$ref": "#/components/schemas/RelatedNode"
            }
          },
          "policies": {
            "type": "array",
            "description": "Add policies to the data set, the policy is the external id of the resource.",
            "example": "policy_is_write_protected",
            "items": {
              "type": "string"
            }
          },
          "connectedDataSets": {
            "type": "array",
            "description": "The ids of the data sets this data set is part of.",
            "example": [
              "2323"
            ],
            "items": {
              "type": "integer",
              "format": "int64"
            }
          },
          "createdTime": {
            "type": "string",
            "format": "date-time",
            "description": "When the object was created, as an ISO-8601 UTC timestamp.",
            "example": "2024-06-17T12:34:56Z",
            "readOnly": true
          },
          "lastUpdatedTime": {
            "type": "string",
            "format": "date-time",
            "description": "When the object was last updated, as an ISO-8601 UTC timestamp.",
            "example": "2024-06-17T12:34:56Z",
            "readOnly": true
          }
        },
        "required": [
          "externalId",
          "labels",
          "name"
        ]
      },
      "Data Set Collection": {
        "type": "object",
        "description": "Data Response with DataSet collection.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Data Set"
            }
          }
        }
      },
      "Data Set Query Filter": {
        "type": "object",
        "description": "Data Set Query Filter Object",
        "properties": {
          "id": {
            "type": "array",
            "description": "Nodes with any of these ids.",
            "example": [
              "12",
              "18"
            ],
            "items": {
              "type": "integer",
              "format": "int64"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "externalId": {
            "type": "array",
            "description": "Nodes matching any of these external ids. `*` and `%` are wildcards; `_` is literal.",
            "example": [
              "sap_work_orders",
              "plant_*"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "name": {
            "type": "array",
            "description": "Nodes whose name matches any of these patterns. `*` and `%` are wildcards.",
            "example": [
              "SAP*",
              "Plant A"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "source": {
            "type": "array",
            "description": "Nodes whose source matches any of these patterns. `*` and `%` are wildcards.",
            "example": [
              "sap",
              "opc_*"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "labels": {
            "type": "array",
            "description": "Nodes carrying all of these labels.",
            "example": [
              "PUMP",
              "CRITICAL"
            ],
            "items": {
              "type": "string"
            },
            "maxItems": 1000,
            "minItems": 0
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Metadata entries that must all be present. A null value matches the key alone.",
            "example": {
              "owner": "plant-a",
              "health": null
            }
          },
          "createdTime": {
            "$ref": "#/components/schemas/TimeFilter"
          },
          "lastUpdatedTime": {
            "$ref": "#/components/schemas/TimeFilter"
          }
        }
      },
      "SearchBodyData Set Query Filter": {
        "type": "object",
        "properties": {
          "search": {
            "$ref": "#/components/schemas/Search Form"
          },
          "filter": {
            "$ref": "#/components/schemas/Data Set Query Filter"
          },
          "limit": {
            "type": "integer",
            "format": "int32",
            "maximum": 1000
          }
        }
      },
      "Data Set Query": {
        "type": "object",
        "description": "Data Set Query Object",
        "properties": {
          "filter": {
            "$ref": "#/components/schemas/Data Set Query Filter"
          },
          "limit": {
            "type": "integer",
            "format": "int32",
            "maximum": 10000
          },
          "sort": {
            "$ref": "#/components/schemas/DataSort"
          },
          "cursor": {
            "type": "string",
            "description": "Opaque cursor from a previous response's `nextCursor`. Must be sent with the same `sort` that produced it.",
            "maxLength": 4096,
            "minLength": 0
          }
        }
      },
      "ValueTypeRecommendation": {
        "type": "object",
        "description": "A suggested timeseries value type for a unit of measure, chosen for the best ClickHouse compression while still representing the data faithfully.",
        "properties": {
          "unitExternalId": {
            "type": "string",
            "description": "The unit externalId the recommendation was made for (echoed from the request).",
            "example": "temperature_deg_c"
          },
          "recommendedValueType": {
            "type": "string",
            "description": "Recommended value type. One of BIGINT, FLOAT, FLOAT32, NUMERIC, DECIMAL32, TEXT or MIXED.",
            "example": "DECIMAL32"
          },
          "reason": {
            "type": "string",
            "description": "Why this value type is recommended for the unit.",
            "example": "Temperature stays well within Decimal32(4)'s range and needs only a few decimals; Decimal32(4) stores it exactly in 4 bytes with the best compression of the numeric types."
          },
          "recognized": {
            "type": "boolean",
            "description": "True if the unit matched a specific recommendation; false means the generic compact default was returned.",
            "example": true
          }
        }
      },
      "TenantFeatures": {
        "type": "object",
        "properties": {
          "files": {
            "type": "boolean"
          },
          "policy": {
            "type": "boolean"
          },
          "streaming": {
            "type": "boolean"
          },
          "chat": {
            "type": "boolean"
          }
        }
      },
      "Governance Template Collection": {
        "type": "object",
        "description": "Data response with a collection of governance templates.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GovernanceTemplateDTO"
            }
          }
        }
      },
      "GovernanceTemplateDTO": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "externalId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "Value Collection": {
        "type": "object",
        "description": "Data response with a collection of distinct values.",
        "properties": {
          "items": {
            "type": "array",
            "example": [
              "alarm",
              "maintenance",
              "inspection"
            ],
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Event Count": {
        "type": "object",
        "description": "The number of events matching the request.",
        "properties": {
          "count": {
            "type": "integer",
            "format": "int64",
            "description": "Number of events.",
            "example": 1423
          }
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "description": "OAuth2 JWT access token (client-credentials for services, or a signed-in session token).",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      }
    }
  },
  "x-tagGroups": [
    {
      "tags": [
        "Labels",
        "Data sets",
        "Governance",
        "Relationships",
        "Units"
      ],
      "name": "Data Organization"
    },
    {
      "tags": [
        "Events",
        "Resources",
        "Time-series",
        "Files"
      ],
      "name": "Asset-Centric Data"
    },
    {
      "tags": [
        "Subscriptions"
      ],
      "name": "Data Streaming"
    }
  ]
}
