This is the full developer documentation for Tier0 # Tier0 Docs > Start building on the Tier0 Unified Namespace — paste one prompt into your code agent, then follow the best practices. Start building with Tier0 ## Paste this into your code agent One prompt installs the Tier0 CLI + skill, logs you in, and takes your agent on a first lap around the Unified Namespace. ``` Run `npx @tier0/cli@latest install` to install the tier0 CLI and agent skill. Read https://docs.tier0.app/llms.txt for the full Tier0 docs. ``` Copy prompt Prefer doing it by hand? [Installation guide](/get-started/installation/)[Demo Factory](/get-started/demo-factory/) Machine-readable endpoints for agents: [/llms.txt](/llms.txt) ## Best Practice Field-tested patterns from real Tier0 deployments. [Modeling](/best-practice/uns-modeling/) ### [UNS Modeling](/best-practice/uns-modeling/) [How the Demo UNS is designed — hierarchy, avoiding topic explosion, and payload structure that scales.](/best-practice/uns-modeling/) [Read more](/best-practice/uns-modeling/) [Protocols](/best-practice/protocol-connections/) ### [Connecting OPC UA / Modbus](/best-practice/protocol-connections/) [Node-RED flows for typical industrial protocols, with real importable flow JSON references.](/best-practice/protocol-connections/) [Read more](/best-practice/protocol-connections/) [Analytics](/best-practice/analytics-apps/) ### [Building Analytics Apps](/best-practice/analytics-apps/) [From Notebook query to interactive analysis app — the Aramco Bowtie case, on sanitized data.](/best-practice/analytics-apps/) [Read more](/best-practice/analytics-apps/) # Building Analytics Apps > Building an analysis app with the Notebook — the Aramco Bowtie case (sanitized data). Tier0 adopts Marimo Notebook to do advanced data analysis with Python, and a Bowtie application is used as an example to demonstrate the process. ``` flowchart LR collect["Node-RED"] -->|"collected data"| uns[("UNS
modeling")] uns -->|"raw data"| notebook["Notebook"] notebook -->|"analysis results"| uns uns -->|"analysis results"| builder["App Builder"] classDef t0accent fill:#EAF8C8,stroke:#A6CF38,stroke-width:1px,color:#171717 classDef t0soft fill:#F7FAF2,stroke:#D8E6B8,stroke-width:1px,color:#2A2A2A classDef t0agent fill:#EEF4FF,stroke:#B7C7E8,stroke-width:1px,color:#1F2937 class uns t0accent class collect,notebook t0soft class builder t0agent ``` ## Example Background [Section titled “Example Background”](#example-background) Refinery corrosion is a complex process influenced by multiple operating factors. This example demonstrates a real-time corrosion risk assessment workflow that combines process data to estimate the likelihood of corrosion and support proactive maintenance. ## Getting Raw Data [Section titled “Getting Raw Data”](#getting-raw-data) 1. In Tier0, go to **UNS**, and import the following models. * Corrosion Monitoring ```json { "name": "Corrosion_Monitoring", "topic": "Aramco/CDU_Plant/Atmospheric_Overhead/Metric/Corrosion_Monitoring", "type": "Metric", "description": "Real-time process measurements for CDU atmospheric overhead corrosion monitoring", "fields": [ { "name": "D103_CHLORIDE", "dataType": "FLOAT", "unit": "ppm", "description": "D-103 Overhead Reflux Drum Chloride concentration" }, { "name": "D103_PH", "dataType": "FLOAT", "unit": "pH", "description": "D-103 Overhead Reflux Drum pH value" }, { "name": "WASH_WATER_FLOW", "dataType": "FLOAT", "unit": "t/h", "description": "Overhead wash water flow" }, { "name": "DESALTER_SALT_PTB", "dataType": "FLOAT", "unit": "PTB", "description": "Salt content in desalted crude" }, { "name": "DESALTER_BSW", "dataType": "FLOAT", "unit": "%", "description": "Basic sediment and water content" }, { "name": "WASH_WATER_RATE", "dataType": "FLOAT", "unit": "%", "description": "Wash water rate" }, { "name": "RRD_PH", "dataType": "FLOAT", "unit": "pH", "description": "Reflux drum sour water pH" }, { "name": "RRD_CHLORIDE", "dataType": "FLOAT", "unit": "ppm", "description": "Reflux drum chloride concentration" }, { "name": "RRD_TOTAL_IRON", "dataType": "FLOAT", "unit": "ppm", "description": "Total iron concentration indicating corrosion" } ] } ``` * Corrosion Risk ```json { "name": "Corrosion_Risk", "topic": "Aramco/CDU_Plant/Atmospheric_Overhead/State/Corrosion_Risk", "type": "State", "description": "Bayesian Network inferred corrosion risk state", "fields": [ { "name": "risk_state", "dataType": "STRING", "description": "Current corrosion risk state: NORMAL, DEVELOPING, CONFIRMED" }, { "name": "previous_state", "dataType": "STRING", "description": "Previous corrosion risk state" }, { "name": "confidence", "dataType": "FLOAT", "unit": "%", "description": "Inference confidence" }, { "name": "timestamp", "dataType": "DATETIME" } ] } ``` * Corrosion Risk Probability ```json { "name": "Corrosion_Risk_Probability", "topic": "Aramco/CDU_Plant/Atmospheric_Overhead/Metric/Corrosion_Risk_Probability", "type": "Metric", "description": "Bayesian Network posterior probability results", "fields": [ { "name": "P_NORMAL", "dataType": "FLOAT" }, { "name": "P_DEVELOPING", "dataType": "FLOAT" }, { "name": "P_CONFIRMED", "dataType": "FLOAT" }, { "name": "LOPC_PROBABILITY", "dataType": "FLOAT" }, { "name": "SHUTDOWN_PROBABILITY", "dataType": "FLOAT" }, { "name": "ESCALATION_PROBABILITY", "dataType": "FLOAT" } ] } ``` 2. Go to **Flows**, create **Source Flow** to connect raw data and publish to **UNS**. *(Node-RED connects data and publishes it to UNS.)* ```json ``` ## Building Analytic App in Notebook [Section titled “Building Analytic App in Notebook”](#building-analytic-app-in-notebook) 1. In Tier0, go to **Notebook**, and create a new notebook. 2. Access the notebook, and add the following cells to analyze data from **UNS**. * Cell 1: Get data from UNS ```python ``` * Cell 2: Validate and organize data ```python ``` * Cell 3: Discretize continuous values ```python ``` * Cell 4: Define Bayesian network structure and CPTs ```python ``` * Cell 5: Build the network and run inference ```python ``` * Cell 6: Organize analysis results ```python ``` * Cell 7: Write results back to UNS through MQTT ```python ``` 3. Run all cells and go to **UNS** to check the results. ## Building Bow-tie App [Section titled “Building Bow-tie App”](#building-bow-tie-app) 1. In Tier0, go to **Builder**. 2. Enter the application requirements in the dialog, and start building. What must be stated Remember to tell the agent to use data from UNS, specific topic paths are necessary. Otherwise the agent might have trouble displaying the correct data. ```plaintext prompt ``` 3. Once the application is complete after certain rounds of refining, click **Deploy** at the upper-right corner. 4. Go to **Launchpad**, open the application and check. # Connecting Industrial Protocols > Connect OPC UA, Modbus, and other typical protocol devices into the UNS with Node-RED — with real flow JSON references. This section takes common data types, inclulding OPC UA, Modbus and API as examples to demonstrate the process of connecting data sources to **UNS** through **Node-RED**. ## OPC UA [Section titled “OPC UA”](#opc-ua) ### Configuring Data Flow [Section titled “Configuring Data Flow”](#configuring-data-flow) 1. Log in to Tier0, go to **Flow**. 2. Click **New Flow** at the upper-right corner, enter a name, select **Flow Type** as **Source Flow** and click **Save**. 3. Access the flow, drag **inject**, **OPC UA Client** and **mqtt out** nodes from the left list to the canvas. 4. Connect them by order and double-click each node to configure the details. * **inject** 1. Click **add** at the lower-left corner to add an entry of property. 2. Set the properties to be the following items: ```json { "msg.payload": "[multiple data tags]", "msg.topic": "multiple" } ``` 3. Click **Done** * **OPC UA Client** 1. Enter the **Endpoint** with the address and port of the OPC UA server. 2. Set the **Action** to be **SUBSCRIBE**. 3. Set the **Interval** of collecting the data, and click **Done**. * **mqtt out** 1. Expand the **Server** drop-down list and select **emqx:1883**. 2. Copy the **topic** path from **UNS**. 3. Click **Done**. 5. Click **Deploy** at the upper-right corner, and trigger the flow by clicking the **inject** node switch. ### Flow Reference [Section titled “Flow Reference”](#flow-reference) Show OPC UA flow JSON ```json [ { "id": "fn_mapper_01", "type": "function", "z": "b002a07d1163c3de", "name": "ISA95 Mapping (Air Compressor)", "func": "// OPC UA NodeId → ISA-95 MQTT Topic Mapping\n\nconst mapping = {\n \"ns=2;i=2\": {\n topic: \"global_plant/Smart_Manufacturing_Group/Suzhou_Plant/Utility_Area/Compressed_Air_System/Air_Compressor_Station/Air_Compressor_01/Metric/outlet_temperature\",\n field: \"temperature\"\n },\n \"ns=2;i=3\": {\n topic: \"global_plant/Smart_Manufacturing_Group/Suzhou_Plant/Utility_Area/Compressed_Air_System/Air_Compressor_Station/Air_Compressor_01/Metric/discharge_pressure\",\n field: \"pressure\"\n },\n \"ns=2;i=4\": {\n topic: \"global_plant/Smart_Manufacturing_Group/Suzhou_Plant/Utility_Area/Compressed_Air_System/Air_Compressor_Station/Air_Compressor_01/Metric/vibration_rms\",\n field: \"vibration\"\n }\n};\n\nlet value = msg.payload;\n\n// OPC UA structure normalize\nif (value && typeof value === \"object\") {\n if (value.value?.value !== undefined) {\n value = value.value.value;\n } else if (value.value !== undefined) {\n value = value.value;\n }\n}\n\nconst m = mapping[msg.topic];\nif (!m) return null;\n\nmsg.topic = m.topic;\n\nmsg.payload = {\n [m.field]: Number(value)\n};\n\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 340, "y": 320, "wires": [ [ "mqtt_out_01" ] ] }, { "id": "mqtt_out_01", "type": "mqtt out", "z": "b002a07d1163c3de", "name": "MQTT Air Compressor Metrics", "topic": "", "qos": "0", "retain": "false", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "broker-djnwpg00y370", "x": 570, "y": 200, "wires": [] }, { "id": "1f242deefa9fdba9", "type": "OpcUa-Client", "z": "b002a07d1163c3de", "endpoint": "298ef5d50e9dc411", "action": "subscribe", "deadbandtype": "a", "deadbandvalue": 1, "time": 10, "timeUnit": "s", "certificate": "n", "localfile": "", "localkeyfile": "", "securitymode": "None", "securitypolicy": "None", "useTransport": false, "maxChunkCount": 1, "maxMessageSize": 8192, "receiveBufferSize": 8192, "sendBufferSize": 8192, "setstatusandtime": false, "keepsessionalive": false, "name": "", "x": 220, "y": 220, "wires": [ [ "fn_mapper_01" ], [], [] ] }, { "id": "7be5f021ed1f10ef", "type": "inject", "z": "b002a07d1163c3de", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "multiple", "payload": "[{\"nodeId\":\"ns=2;i=2\",\"datatype\":\"Float\"},{\"nodeId\":\"ns=2;i=3\",\"datatype\":\"Float\"},{\"nodeId\":\"ns=2;i=4\",\"datatype\":\"Float\"}]", "payloadType": "json", "x": 110, "y": 160, "wires": [ [ "1f242deefa9fdba9" ] ] }, { "id": "broker-djnwpg00y370", "type": "mqtt-broker", "z": "b002a07d1163c3de", "name": "emqx:1883", "broker": "emqx", "port": "1883", "clientid": "355424069147520", "usetls": false, "protocolVersion": "4", "keepalive": "60", "cleansession": true, "birthTopic": "", "birthQos": "0", "birthPayload": "", "closeTopic": "", "closeQos": "0", "closePayload": "", "willTopic": "", "willQos": "0", "willPayload": "" }, { "id": "298ef5d50e9dc411", "type": "OpcUa-Endpoint", "endpoint": "opc.tcp://172.31.151.237:4841", "secpol": "None", "secmode": "None", "none": true, "login": false, "usercert": false, "usercertificate": "", "userprivatekey": "" }, { "id": "a003de8af8e62c75", "type": "global-config", "env": [], "modules": { "node-red-contrib-opcua": "0.2.339" } } ] ``` ## Modbus [Section titled “Modbus”](#modbus) ### Configuring Data Flow [Section titled “Configuring Data Flow”](#configuring-data-flow-1) 1. In the flow, drag a **modbus-read** node from the left list to the canvas. 2. Double-click the node, and configure the required information. * **Settings** * **FC**: Function code that specifies the Modbus action, e.g., FC 3 = Read Holding Registers. * **Address**: The starting register address to read from (usually zero-based). * **Quantity**: The number of consecutive registers to read. * **Poll Rate**: How often the node polls the Modbus device (e.g., every 10 seconds). * **Server**: Reference to a configured Modbus server (IP, port, protocol, etc.). * **Server** * **Host**: Modbus server IP. * **Port**: Modbus server port. 3. Click **Done** to finish node configuration, and drag in an **mqtt out** node to the canvas. 4. Double-click **mqtt out**, expand the **Server** drop-down list and select **emqx:1883**. 5. Copy the **topic** path from **UNS**, click **Done** and click **Deploy** at the upper-right corner. 6. Click the switch of the **modbus read** node to trigger the flow, and go back to **UNS** to check the result. Other things you might want to know * You can drag a **function** node to clean the data as needed before publishing to **UNS**. * A **debug** node can be used to check the data on the spot. * Details about the nodes, please refer to [Node-RED flows](https://flows.nodered.org/). ### Flow Reference [Section titled “Flow Reference”](#flow-reference-1) Show Modbus flow JSON ```json [ { "id": "d3becb19e0d762f4", "type": "modbus-read", "z": "b002a07d1163c3de", "name": "status", "topic": "", "showStatusActivities": false, "logIOActivities": false, "showErrors": false, "showWarnings": true, "unitid": "1", "dataType": "HoldingRegister", "adr": "0", "quantity": "1", "rate": "10", "rateUnit": "s", "delayOnStart": false, "startDelayTime": "", "server": "c665b74952f6cf5a", "useIOFile": false, "ioFile": "", "useIOForPayload": false, "emptyMsgOnFail": false, "x": 130, "y": 500, "wires": [ [ "2987638967f632b4", "09b8538e09d0766a" ], [] ] }, { "id": "b068c119928c029a", "type": "mqtt out", "z": "b002a07d1163c3de", "name": "status", "topic": "global_plant/Smart_Manufacturing_Group/Suzhou_Plant/Utility_Area/Compressed_Air_System/Air_Compressor_Station/Air_Compressor_01/State/compressor_status", "qos": "", "retain": "", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "broker-djnwpg00y370", "x": 510, "y": 540, "wires": [] }, { "id": "2987638967f632b4", "type": "debug", "z": "b002a07d1163c3de", "name": "debug 3", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 260, "y": 600, "wires": [] }, { "id": "09b8538e09d0766a", "type": "function", "z": "b002a07d1163c3de", "name": "function 1", "func": "const value = msg.payload;\n\n// 1️⃣ verify\nif (value === undefined || value === null) {\n node.warn(\"Empty Modbus payload\");\n return null;\n}\n\n// 2️⃣ convert to number\nconst status_code = Number(value);\n\n// 3️⃣ mapping to status\nconst statusMap = {\n 0: \"maintenance\",\n 1: \"running\",\n 2: \"standby\",\n 3: \"debug\",\n 4: \"fault\"\n};\n\n// 4️⃣ filtering\nif (!(status_code in statusMap)) {\n node.warn(\"Invalid status_code: \" + status_code);\n return null;\n}\n\n// 5️⃣ contextualized output\nmsg.payload = {\n status_code: status_code,\n status_label: statusMap[status_code],\n _valid: true,\n _ts: new Date().toISOString()\n};\n\n// 6️⃣ optional set topic\nmsg.topic = \"v1/aircompressor01/state\";\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 320, "y": 500, "wires": [ [ "b068c119928c029a" ] ] }, { "id": "c665b74952f6cf5a", "type": "modbus-client", "name": "modbus", "clienttype": "tcp", "bufferCommands": true, "stateLogEnabled": false, "queueLogEnabled": false, "failureLogEnabled": true, "tcpHost": "172.31.151.237", "tcpPort": "5020", "tcpType": "DEFAULT", "serialPort": "/dev/ttyUSB", "serialType": "RTU-BUFFERD", "serialBaudrate": 9600, "serialDatabits": 8, "serialStopbits": 1, "serialParity": "none", "serialConnectionDelay": 100, "serialAsciiResponseStartDelimiter": "0x3A", "unit_id": 1, "commandDelay": 1, "clientTimeout": 1000, "reconnectOnTimeout": true, "reconnectTimeout": 2000, "parallelUnitIdsAllowed": true, "showErrors": false, "showWarnings": true, "showLogs": true }, { "id": "broker-djnwpg00y370", "type": "mqtt-broker", "z": "b002a07d1163c3de", "name": "emqx:1883", "broker": "emqx", "port": "1883", "clientid": "355424069147520", "usetls": false, "protocolVersion": "4", "keepalive": "60", "cleansession": true, "birthTopic": "", "birthQos": "0", "birthPayload": "", "closeTopic": "", "closeQos": "0", "closePayload": "", "willTopic": "", "willQos": "0", "willPayload": "" }, { "id": "5cc203d0788c324d", "type": "global-config", "env": [], "modules": { "node-red-contrib-modbus": "5.43.0" } } ] ``` ## API [Section titled “API”](#api) ### Configuring Data Flow [Section titled “Configuring Data Flow”](#configuring-data-flow-2) 1. In the flow, drag a **modbus-read** node from the left list to the canvas. 2. Double-click the node, and configure the required information. * **Settings** * **FC**: Function code that specifies the Modbus action, e.g., FC 3 = Read Holding Registers. * **Address**: The starting register address to read from (usually zero-based). * **Quantity**: The number of consecutive registers to read. * **Poll Rate**: How often the node polls the Modbus device (e.g., every 10 seconds). * **Server**: Reference to a configured Modbus server (IP, port, protocol, etc.). * **Server** * **Host**: Modbus server IP. * **Port**: Modbus server port. 3. Click **Deploy** at the upper-right corner, and trigger the flow by clicking the **inject** node switch. Other things you might want to know * You can drag a **function** node to clean the data as needed before publishing to **UNS**. * A **debug** node can be used to check the data on the spot. * Details about the nodes, please refer to [Node-RED flows](https://flows.nodered.org/). ### Flow Reference [Section titled “Flow Reference”](#flow-reference-2) Show API flow JSON ```json [ { "id": "46273fcbb98afa11", "type": "inject", "z": "932355c6886aaf27", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 170, "y": 380, "wires": [ [ "3aca156076ad6dd1" ] ] }, { "id": "3aca156076ad6dd1", "type": "http request", "z": "932355c6886aaf27", "name": "", "method": "GET", "ret": "obj", "paytoqs": "ignore", "url": "http://127.0.0.1:1880/api/wms/picking-zone-a/status", "tls": "", "persist": false, "proxy": "", "insecureHTTPParser": false, "authType": "", "senderr": false, "headers": [], "x": 380, "y": 380, "wires": [ [ "83fd65cf177c2ddf" ] ] }, { "id": "83fd65cf177c2ddf", "type": "mqtt out", "z": "932355c6886aaf27", "name": "", "topic": "v1/Suzhou_Plant/Assembly-Area-1/Line-01/Station-Screwing/Metric/torque", "qos": "", "retain": "", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "broker-djm245hxt9d9", "x": 780, "y": 440, "wires": [] }, { "id": "broker-djm245hxt9d9", "type": "mqtt-broker", "name": "emqx:1883", "broker": "emqx", "port": "1883", "clientid": "355039328257392", "usetls": false, "protocolVersion": "4", "keepalive": "60", "cleansession": true, "birthTopic": "", "birthQos": "0", "birthPayload": "", "closeTopic": "", "closeQos": "0", "closePayload": "", "willTopic": "", "willQos": "0", "willPayload": "" } ] ``` # UNS Modeling > The Demo UNS design: avoiding topic explosion and payload structure best practices. ## How the Demo UNS is Structured [Section titled “How the Demo UNS is Structured”](#how-the-demo-uns-is-structured) What needs to be done first Before modeling data, answer the following questions. * What data will be consumed from the UNS? * What data will be published back to the UNS? ### Asset Data [Section titled “Asset Data”](#asset-data) Industrial data from machines and equipment, assets that have physical locations, is modeled based on the ISA-95 standard. #### ISA-95 Equipment Hierarchy [Section titled “ISA-95 Equipment Hierarchy”](#isa-95-equipment-hierarchy) The ISA-95 Equipment Hierarchy outlines how physical assets and control systems are structured within an organization. This structure ensures that each level interacts appropriately with higher and lower levels, creating a clear operational flow from the enterprise systems down to individual machines.\ The ISA-95 equipment hierarchy is broken into the following levels: * **Enterprise**: The top level, representing the entire organization. This includes overall business processes and decision-making entities, typically supported by ERP systems. * **Site**: Represents a specific geographic location or facility. Each site can have its own management systems and local production control. * **Area**: An operational division within a site, often a functional area such as packaging, assembly, or warehousing. * **Process Cell**: The basic unit of manufacturing within an area. It encompasses all the equipment required to produce a specific product or perform a specific function. * **Unit**: A distinct part of the process cell, responsible for a specific function such as mixing, heating, or assembling. * **Equipment Module**: A modular piece of equipment that performs a specific task within a unit. For example, in a mixing unit, the module could be a heating element. * **Control Module**: The lowest level representing individual devices or controllers such as sensors, pumps, actuators or PID control loops that handle direct control of physical processes. #### Demo Model [Section titled “Demo Model”](#demo-model) * ISA-95 Hierarchy ```text Enterprise (Level 4) └── Site (Level 3/4) └── Area (Level 3) └── Work Center / Process Cell (Level 2/3) └── Equipment / Unit (Level 1/2) └── Control Module / Device (Level 0/1) ``` * Tree Structure ```text DemoFactory └── Site_01 └── Production └── Line_01 └── WorkOrderExecution ├── Metric │ ├── Target_Qty │ ├── Produced_Qty │ ├── Defect_Qty │ └── Completion_Rate └── State ├── CurrentWorkOrder └── WorkOrderStatus ``` * JSON ```json { "namespace": [ { "name": "DemoFactory", "alias": "DemoFactory_9e50bbcd833d", "type": "PATH", "children": [ { "name": "Site_01", "alias": "DemoFactory__6d1fcad14e86", "type": "PATH", "children": [ { "name": "Production", "alias": "__Site_01_Pro_91523d2ad304", "type": "PATH", "children": [ { "name": "Line_01", "alias": "__Production__acd5841c1ada", "type": "PATH", "children": [ { "name": "WorkOrderExecution", "alias": "__Line_01_Wor_54904ff56e91", "type": "PATH", "children": [ { "name": "Metric", "alias": "metric_233fd84305c4", "type": "PATH", "children": [ { "name": "Target_Qty", "alias": "Target_Qty_8646b092e543", "type": "TOPIC", "topicType": "METRIC", "description": "Target quantity for the current Work Order.", "fields": [ { "name": "production_order_id", "type": "STRING" }, { "name": "plan_id", "type": "STRING" }, { "name": "plan_number", "type": "STRING" }, { "name": "work_order_id", "type": "STRING" }, { "name": "value", "type": "DOUBLE" }, { "name": "unit", "type": "STRING" }, { "name": "timestamp", "type": "DATETIME" } ], "enableHistory": "TRUE", "mockData": "FALSE" }, { "name": "Produced_Qty", "alias": "Produced_Qty_1f8cd3e11f77", "type": "TOPIC", "topicType": "METRIC", "description": "Produced quantity for the current Work Order.", "fields": [ { "name": "production_order_id", "type": "STRING" }, { "name": "plan_id", "type": "STRING" }, { "name": "plan_number", "type": "STRING" }, { "name": "work_order_id", "type": "STRING" }, { "name": "value", "type": "DOUBLE" }, { "name": "unit", "type": "STRING" }, { "name": "timestamp", "type": "DATETIME" } ], "enableHistory": "TRUE", "mockData": "FALSE" }, { "name": "Defect_Qty", "alias": "Defect_Qty_b48fb3563ec1", "type": "TOPIC", "topicType": "METRIC", "description": "Defect quantity for the current Work Order.", "fields": [ { "name": "production_order_id", "type": "STRING" }, { "name": "plan_id", "type": "STRING" }, { "name": "plan_number", "type": "STRING" }, { "name": "work_order_id", "type": "STRING" }, { "name": "value", "type": "DOUBLE" }, { "name": "unit", "type": "STRING" }, { "name": "timestamp", "type": "DATETIME" } ], "enableHistory": "TRUE", "mockData": "FALSE" }, { "name": "Completion_Rate", "alias": "Completion_R_7294e38e1530", "type": "TOPIC", "topicType": "METRIC", "description": "Completion percentage for the current Work Order.", "fields": [ { "name": "production_order_id", "type": "STRING" }, { "name": "plan_id", "type": "STRING" }, { "name": "plan_number", "type": "STRING" }, { "name": "work_order_id", "type": "STRING" }, { "name": "value", "type": "DOUBLE" }, { "name": "unit", "type": "STRING" }, { "name": "timestamp", "type": "DATETIME" } ], "enableHistory": "TRUE", "mockData": "FALSE" } ] }, { "name": "State", "alias": "state_539a0f331fe0", "type": "PATH", "children": [ { "name": "CurrentWorkOrder", "alias": "CurrentWorkO_a638320784dd", "type": "TOPIC", "topicType": "STATE", "description": "Current active Work Order on Line_01.", "fields": [ { "name": "production_order_id", "type": "STRING" }, { "name": "plan_id", "type": "STRING" }, { "name": "plan_number", "type": "STRING" }, { "name": "work_order_id", "type": "STRING" }, { "name": "target_line", "type": "STRING" }, { "name": "workstation", "type": "STRING" }, { "name": "product_code", "type": "STRING" }, { "name": "target_qty", "type": "DOUBLE" }, { "name": "unit", "type": "STRING" }, { "name": "started_at", "type": "DATETIME" }, { "name": "updated_at", "type": "DATETIME" } ], "enableHistory": "TRUE", "mockData": "FALSE" }, { "name": "WorkOrderStatus", "alias": "WorkOrderSta_e24e3bf113d1", "type": "TOPIC", "topicType": "STATE", "description": "Execution status of the current Work Order.", "fields": [ { "name": "production_order_id", "type": "STRING" }, { "name": "plan_id", "type": "STRING" }, { "name": "plan_number", "type": "STRING" }, { "name": "work_order_id", "type": "STRING" }, { "name": "status", "type": "STRING" }, { "name": "reason", "type": "STRING" }, { "name": "updated_at", "type": "DATETIME" } ], "enableHistory": "TRUE", "mockData": "FALSE" } ] } ] } ] } ] } ] } ] } ] } ``` ### Business Data [Section titled “Business Data”](#business-data) For data related business plan and process objects, such as production orders and order plan in an ERP, model it according to business context. How to recognize business data Data from something that does not have physical locations. #### Business Data Modeling Guide [Section titled “Business Data Modeling Guide”](#business-data-modeling-guide) ```plaintext Factory / Business Domain / Business Object / topic Type / Topic ``` * **Business Domain**: Confirm the business process or system the data belongs to. ```plaintext ERP MES WMS QMS EAM LIMS Planning Inventory Maintenance Quality Logistics ... ``` * **Business Object**: Managed core object in the business domain. ```plaintext ProductionOrders WorkOrderPlan MaterialInventory QualityInspection MaintenanceOrders PurchaseOrders Shipments ... ``` * **Topic Type**: Decide the data type among Metric, Action and State. | Topic Type | Used for | Examples | | ---------- | ------------------------------------ | ------------------------------------------------------------------------- | | STATE | Current status or current facts | Work order status, order status, equipment status | | METRIC | Continuously changing numeric values | Temperature, speed, output, OEE, energy consumption | | ACTION | Commands for external executors | Start equipment, stop equipment, reset alarm, request work order dispatch | #### Demo Model [Section titled “Demo Model”](#demo-model-1) * Tree Structure ```text DemoFactory └── ERP ├── ProductionOrders │ └── State │ ├── UpsertProductionOrder │ └── OrderList └── WorkOrderPlan ├── Metric │ └── SplitCount └── State ├── PlanStatus └── WorkOrderList ``` * JSON ```json { "namespace": [ { "name": "DemoFactory", "alias": "DemoFactory_f925c1ec5041", "type": "PATH", "children": [ { "name": "ERP", "alias": "DemoFactory__e504805b1973", "type": "PATH", "children": [ { "name": "ProductionOrders", "alias": "__ERP_Product_1e7984ac3a1d", "type": "PATH", "children": [ { "name": "State", "alias": "state_c2616c8698fa", "type": "PATH", "children": [ { "name": "UpsertProductionOrder", "alias": "UpsertProduc_d5f166da35c6", "type": "TOPIC", "topicType": "STATE", "description": "Event for creating or updating a Production Order from WOM or ERP simulation.", "fields": [ { "name": "event_id", "type": "STRING" }, { "name": "event_type", "type": "STRING" }, { "name": "source", "type": "STRING" }, { "name": "production_order_id", "type": "STRING" }, { "name": "order_number", "type": "STRING" }, { "name": "product_code", "type": "STRING" }, { "name": "planned_qty", "type": "DOUBLE" }, { "name": "unit", "type": "STRING" }, { "name": "status", "type": "STRING" }, { "name": "due_date", "type": "DATETIME" }, { "name": "updated_at", "type": "DATETIME" } ], "enableHistory": "TRUE", "mockData": "FALSE" }, { "name": "OrderList", "alias": "OrderList_11fbca51a2bd", "type": "TOPIC", "topicType": "STATE", "description": "Retained compatibility snapshot of current Production Orders.", "fields": [ { "name": "orders", "type": "STRING" }, { "name": "updated_at", "type": "DATETIME" } ], "enableHistory": "TRUE", "mockData": "FALSE" } ] } ] }, { "name": "WorkOrderPlan", "alias": "__ERP_WorkOrd_19f04960daf5", "type": "PATH", "children": [ { "name": "Metric", "alias": "metric_17258b809a4f", "type": "PATH", "children": [ { "name": "SplitCount", "alias": "SplitCount_032e86f5fbfc", "type": "TOPIC", "topicType": "METRIC", "description": "Number of executable Work Orders generated from a Work Order Plan.", "fields": [ { "name": "production_order_id", "type": "STRING" }, { "name": "plan_id", "type": "STRING" }, { "name": "plan_number", "type": "STRING" }, { "name": "split_count", "type": "INTEGER" }, { "name": "updated_at", "type": "DATETIME" } ], "enableHistory": "TRUE", "mockData": "FALSE" } ] }, { "name": "State", "alias": "state_884cee548bf7", "type": "PATH", "children": [ { "name": "PlanStatus", "alias": "PlanStatus_2e21c1327c49", "type": "TOPIC", "topicType": "STATE", "description": "Current lifecycle status of a Work Order Plan.", "fields": [ { "name": "production_order_id", "type": "STRING" }, { "name": "plan_id", "type": "STRING" }, { "name": "plan_number", "type": "STRING" }, { "name": "status", "type": "STRING" }, { "name": "updated_at", "type": "DATETIME" } ], "enableHistory": "TRUE", "mockData": "FALSE" }, { "name": "WorkOrderList", "alias": "WorkOrderLis_887b92f26fc2", "type": "TOPIC", "topicType": "STATE", "description": "Split result list generated from a sent Work Order Plan.", "fields": [ { "name": "production_order_id", "type": "STRING" }, { "name": "plan_id", "type": "STRING" }, { "name": "plan_number", "type": "STRING" }, { "name": "split_count", "type": "INTEGER" }, { "name": "lines", "type": "STRING" }, { "name": "updated_at", "type": "DATETIME" } ], "enableHistory": "TRUE", "mockData": "FALSE" } ] } ] } ] } ] } ] } ``` ## How to Avoid Topic Explosion [Section titled “How to Avoid Topic Explosion”](#how-to-avoid-topic-explosion) Tier0 uses 3 fixed topic types: `Metric`, `Action`, and `State` to avoid topic explosion. * **Use `Metric` for high-frequency real-time data** Put high-frequency measurements and telemetry under `Metric` instead of expanding the namespace for every signal detail. * Bad ```text Factory_A └── SMT_Line_1 └── Machine_001 ├── Temperature_Value ├── Temperature_Unit ├── Temperature_Status └── Speed_Value ``` * Better ```text Factory_A └── SMT_Line_1 └── Metric └── Machine_001 ``` Model fields: ```json { "temperature": 80, "temperature_unit": "Celsius", "temperature_status": "Normal", "speed": 300 } ``` * **Use `State` for low-frequency status data** Put low-frequency status, snapshots, and current business state under `State`. Avoid creating a new topic for every changing entity such as jobs, batches, or orders. * Bad ```text Factory_A └── SMT_Line_1 └── State ├── Job_001 ├── Job_002 └── Job_003 ``` * Better ```text Factory_A └── SMT_Line_1 └── State └── Machine_001 ``` Model fields: ```json { "current_job": "Job_003", "product": "Product_A" } ``` * **Use `Action` for low-frequency action data** Put commands, requests, and operator actions under `Action`. Keep action inputs as fields on a topic rather than creating one topic per parameter. * Bad ```text Factory_A └── SMT_Line_1 └── Machine_001 ├── Start_Command ├── Target_Height └── Light_On ``` * Better ```text Factory_A └── SMT_Line_1 └── Action └── StartJob ``` Model fields: ```json { "target_height": 12.5, "light_on": true, "requested_by": "operator" } ``` ## Payload Structure [Section titled “Payload Structure”](#payload-structure) Data to be consumed and written back must both be considered when structuring the topic payload. * **Action** Action payloads automatically include `_timestamp`, which records when the data is stored in the Tier0 database. * start\_job ```json { "light_turn_on": true, "height_adjust": 12.5, "_timestamp": "2026-07-10T10:00:00Z" } ``` * stop\_job ```json { "water_pump_off": true, "window_close": true, "_timestamp": "2026-07-10T10:00:00Z" } ``` * **Metric** Metric payloads automatically include `_timestamp`, which records when the data is stored in the Tier0 database, and `_quality`, which maps the data quality. * ERP / SplitCount ```json { "production_order_id": "PO-20260710-001", "plan_id": "PLAN-001", "plan_number": "WOP-20260710-001", "split_count": 3, "updated_at": "2026-07-10T10:00:00Z", "_timestamp": "2026-07-10T10:00:00Z", "_quality": "Good" } ``` * Site\_01 / Target\_Qty ```json { "production_order_id": "PO-20260710-001", "plan_id": "PLAN-001", "plan_number": "WOP-20260710-001", "work_order_id": "WO-001", "value": 1200, "unit": "pcs", "timestamp": "2026-07-10T10:00:00Z", "_timestamp": "2026-07-10T10:00:00Z", "_quality": "Good" } ``` * **State** State payloads automatically include `_timestamp`, which records when the data is stored in the Tier0 database. * ERP / UpsertProductionOrder ```json { "event_id": "EVT-001", "event_type": "UPSERT", "source": "ERP", "production_order_id": "PO-20260710-001", "order_number": "PO-001", "product_code": "SKU-1001", "planned_qty": 1200, "unit": "pcs", "status": "Released", "due_date": "2026-07-12T00:00:00Z", "updated_at": "2026-07-10T10:00:00Z", "_timestamp": "2026-07-10T10:00:00Z" } ``` * Site\_01 / WorkOrderStatus ```json { "production_order_id": "PO-20260710-001", "plan_id": "PLAN-001", "plan_number": "WOP-20260710-001", "work_order_id": "WO-001", "status": "Running", "reason": "In production", "updated_at": "2026-07-10T10:00:00Z", "_timestamp": "2026-07-10T10:00:00Z" } ``` # Choosing the Best Version > Edge, Cloud, or Enterprise — positioning, plans, and a capability matrix for picking your Tier0 edition. Tier0 is one platform in three editions. Same namespace model, same flows, same CLI — the difference is who runs it and how much platform you get. Open source ### Edge The UNS foundation, on one machine. Yours entirely. For technical evaluation, PoCs, and teams comfortable operating open-source software on their own. Apache-2.0free UNS core, SourceFlows/EventFlows, history storage — single-machine Docker deployment. [Clone on GitHub](https://github.com/FREEZONEX/Tier0-Edge) Managed SaaS ### Cloud Most teams start here The full platform, operated for you. Apps, notebooks, and launchpad on day one — no infrastructure to run. Growth ★$20,000/yr Up to 5 edges. For a single factory with a few apps, wanting a quick start. Scale$38,000/yr Up to 10 edges. For users with multiple factories and many apps. [Start the 14-day trial](https://tier0.app/cloud-trial) Private deployment ### Enterprise The full platform, on your terms. For data sovereignty, scale, governance, and enterprise-grade oversight. Base$10,000/yr Data Foundation. A small number of single-purpose apps — e.g. energy monitoring, equipment management. Plus$20,000/yr Single Instance. Single-factory data integration, apps across multiple use cases. Cluster ★$39,900+/yr Multi-Instance. Multiple factories, many apps, centralized private-cloud management. [Talk to the team](https://tier0.app/talk-to-team) **Add-ons:** extra edge nodes $2,000 /edge/year · additional instances $10,000 /instance/year. Prices are for orientation — the source of truth is [tier0.app/pricing](https://tier0.app/pricing). ## Edge hardware requirements [Section titled “Edge hardware requirements”](#edge-hardware-requirements) TODO — 写作线索 (Huize) Edge:适合进行技术测试,或具备充分技术能力的用户。要加入硬件要求。 | | Minimum | Recommended | | ------ | ------------------------------------- | ----------- | | CPU | 4 cores | 8 cores | | Memory | 8 GB | 16 GB | | Disk | 100 GB (1000 IOPS) | 1 TB | | OS | Ubuntu 24.04 · Windows 10/11 (Docker) | — | ## Not sure? Trace the path [Section titled “Not sure? Trace the path”](#not-sure-trace-the-path) ``` flowchart TD q1{"Technical evaluation,
or strong in-house team?"} -- "yes" --> edge["Edge (open source)"] q1 -- "no" --> q2{"Need on-prem /
data sovereignty?"} q2 -- "no" --> q3{"More than 5 edges?"} q2 -- "yes" --> q4{"Multiple factories /
need HA?"} q3 -- "no" --> growth["Cloud Growth ★"] q3 -- "yes" --> scale["Cloud Scale"] q4 -- "yes" --> cluster["Enterprise Cluster ★"] q4 -- "no" --> baseplus["Enterprise Base / Plus"] class growth,cluster t0accent ``` ## Capability matrix [Section titled “Capability matrix”](#capability-matrix) | Capability | Edge | Cloud | Enterprise | | ------------------------------------------ | ---------------- | --------- | --------------------- | | UNS core (semantic MQTT namespace) | ✓ | ✓ | ✓ | | SourceFlow / EventFlow (Node-RED) | ✓ | ✓ | ✓ | | History storage (TimescaleDB / PostgreSQL) | ✓ single machine | ✓ managed | ✓ your infrastructure | | `tier0` CLI + agent skills | ✓ | ✓ | ✓ | | App Builder + App Library | — | ✓ | ✓ | | Notebook (Advanced Analysis) | — | ✓ | ✓ | | Launchpad (front-line apps) | — | ✓ | ✓ | | HA / multi-instance / governance | — | — | ✓ Cluster | | Operations | you | FREEZONEX | you, with support | ## What’s an “edge” (the unit)? [Section titled “What’s an “edge” (the unit)?”](#whats-an-edge-the-unit) In Cloud plans, an *edge* is a connection point that collects data close to your equipment — typically a gateway or industrial PC running collection flows — and publishes into the namespace. Count roughly one per site or isolated network segment. Not to be confused with the **Edge edition** (the open-source distribution above). ## Next [Section titled “Next”](#next) * [Build Apps on UNS](/using-tier0/build-apps/) — deploying containers on Edge and Enterprise * [Installation](/get-started/installation/) — the 14-day Cloud trial is the full platform # Try the Demo Factory in Tier0 > Explore a preloaded, working plant in your Cloud trial workspace — namespace, flows, notebooks, and apps. The quickest way to *get* Tier0 is to poke at a factory that already works. Your **Cloud trial workspace** (14 days) comes with a preloaded plant: A live namespace, flows feeding it with industrial data, notebooks, and applications working on top of the UNS data foundation. 1. **Browse the namespace.** Open the UNS explorer, or from your terminal: ```bash tier0 uns browse tier0 uns browse Plant/Line1 --max-depth 2 ``` Paths are folders (`Plant/Line1`); leaves are topics (`Plant/Line1/Metric/Temperature`). Only topics hold values. 2. **Read live values.** The namespace is MQTT pub/sub underneath — data moves when it changes: ```bash tier0 uns read "Plant/+/Metric/Temperature" --json ``` 3. **See where data comes from.** Flow names mirror the topic paths they feed (`Line1-Collector` → `Plant/Line1/…`): ```bash tier0 flow list --source ``` 4. **Open a notebook.** Query the same topics live — group by asset, plot a trend, join metrics with order context. See [Analyze UNS Data](/using-tier0/analyze-data/). 5. **Generate an app.** Describe something small in the App Builder — an inspection checklist, a maintenance form — review the spec, generate, and try it in the sandbox. Note The exact demo tree and preloaded apps may differ per trial workspace — substitute what you see in `tier0 uns browse`. On **Edge** there’s no preloaded factory: create your first nodes with `tier0 uns create` instead, then follow [Connect Data to UNS](/using-tier0/connect-data/). ## Next [Section titled “Next”](#next) * [Choosing the Best Version](/get-started/choosing-version/) * [UNS Concepts](/using-tier0/uns-concepts/) — understand what you just browsed # Installation > Install tier0 by editions. Tier0 ships in 3 editions: * **Edge** - Open-source deployment, runs locally with Docker. * **Cloud** - Managed SaaS, no infrastructure required. * **Enterprise** - Private deployment for enterprise environments. See [Choosing the Best Version](/get-started/choosing-version/) and find one that best fits your requirements. ## Installing Tier0 [Section titled “Installing Tier0”](#installing-tier0) * Cloud ### Requirements (Cloud) [Section titled “Requirements (Cloud)”](#requirements-cloud) * Trial account application and email verification You need to apply for a trial account before exploring. 1. Register your account at [Register Tier0](https://tier0.dev/signup). 2. Enter the verification code sent to your email on the popup window. 3. You will be redirected to the **Login** page. Log in with the registered account. 4. Enter related information, click **Continue**. 5. Select **Create a New Workspace**. 6. Create a workspace and start your trial. * Edge ### Requirements (Edge) [Section titled “Requirements (Edge)”](#requirements-edge) * Minimum 4-core CPU, 8 GB RAM, 100 GB disk * Ubuntu 24.04 or Windows 10/11 with Docker and Git #### Operations [Section titled “Operations”](#operations) 1. Clone the Tier0-Edge repository from GitHub and access the project. ```bash git clone https://github.com/FREEZONEX/Tier0-Edge cd Tier0-Edge/deploy ``` 2. Configure parameters indicated in the table below according to your environment in `.env.default`. ```bash vi .env.default :wq! ``` 3. Run the installation script. ```bash bash bin/install.sh ``` Parameters to be configured | Field | Type | Description | | ------------------------------- | ------ | ------------------------------------------------------------ | | VOLUMES\_PATH | string | Storage path for project data | | ENTRANCE\_DOMAIN/ENTRANCE\_PORT | string | Tier0 access endpoint (IP/domain + port) | | OS\_PLATFORM\_TYPE | enum | OS where Tier0 is installed. Can be **linux** or **windows** | | LANGUAGE | enum | zh-CN / en-US | * Enterprise ### Requirements (Enterprise) [Section titled “Requirements (Enterprise)”](#requirements-enterprise) #### Standard Deployment [Section titled “Standard Deployment”](#standard-deployment) * Minimum: 4 VMs * Required baseline per VM types: * App: 8C / 32G / 100G * Database: 4C / 8G / 500G * Witness: 2C / 4G / 40G #### High Availability Deployment [Section titled “High Availability Deployment”](#high-availability-deployment) * Minimum: 6 VMs * Required baseline per VM types: * App (Swarm): 8C / 32G / 100G ×3 * Database: 4C / 8G / 500G ×2 * Witness: 2C / 4G / 40G #### Operations [Section titled “Operations”](#operations-1) Detailed installation instructions are available in the [Enterprise Installation Guide](#). ## Next [Section titled “Next”](#next) * [Try the Demo Factory](/get-started/demo-factory/) — a preloaded plant to explore * [Choosing the Best Version](/get-started/choosing-version/) # Operate Tier0 with Agents > What the Tier0 Skill can do and how to install it. Tier0 provides a set of skills and CLI commands for agents to operate on it. ## Installing Tier0 Skills [Section titled “Installing Tier0 Skills”](#installing-tier0-skills) > Required CLI version: `v0.4.6+` Choose the installer that matches your environment. ### With Node.js [Section titled “With Node.js”](#with-nodejs) Use `npx` when Node.js >= 16 is available to install. ```bash npx @tier0/cli@latest ``` This runs the full installer and does two things: * Installs the `tier0` CLI binary into `~/.tier0/bin/`. * Installs Cursor/Claude Agent Skills from `FREEZONEX/Tier0-skill`. If the Agent Skills registration step fails If the Agent Skills registration step fails, it does not affect the CLI installation. Run the skills installer manually: ```bash npx skills add FREEZONEX/Tier0-skill ``` ### Without Node.js [Section titled “Without Node.js”](#without-nodejs) 1. Install the `tier0` CLI binary. * curl ```bash curl -fsSL https://raw.githubusercontent.com/FREEZONEX/Tier0-cli/main/install.sh | bash ``` * iwr ```powershell iwr https://raw.githubusercontent.com/FREEZONEX/Tier0-cli/main/install.ps1 | iex ``` 2. Install the Agent Skills. ```bash tier0 skills update ``` ## What the Tier0 Skill Can Do [Section titled “What the Tier0 Skill Can Do”](#what-the-tier0-skill-can-do) The Tier0 Skill gives agents a structured way to choose the right CLI command, read the matching reference, and operate Tier0 through the `tier0` CLI. ### Setting Operating Instance [Section titled “Setting Operating Instance”](#setting-operating-instance) Note SaaS uses `https://tier0.dev` by default and needs no base URL configuration. Private deployments must configure the base URL before login. Once it’s configured, all the following commands will be executed on that instance. ```bash tier0 config --base-url https://your-tier0.example.com ``` ### Logging In [Section titled “Logging In”](#logging-in) * With an API key 1. Set the API key for the operating instance. ```bash tier0 config --api-key ``` 2. Verify the API key identity and permissions. ```bash tier0 auth whoami ``` 3. Perform other operations with the `tier0` CLI. * Without an API key 1. Start the browser login. ```bash tier0 login --no-wait --json ``` 2. Go through authentication on the browser by accessing the returned `verification_url`. 3. Poll for the login result with the returned `setup_code`. ```bash tier0 login --setup-code ``` Important Run `config` to set the operating instance before configuring an API key or starting browser login; otherwise the CLI may operate on the wrong instance. | Target | Task | Commands | | ---------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Basic operations | Check CLI and service health | `tier0 doctor` | | Basic operations | Check service connectivity and gateway info | `tier0 api /openapi/v1/info --body '{}' --json` | | Basic operations | Upgrade CLI and skills | `tier0 upgrade` and `tier0 skills update` | | UNS | Browse paths | `tier0 uns browse --path /` or `tier0 uns browse --path Plant/Line1 --json` | | UNS | Search for topics by keywords | `tier0 uns search --keyword temp --json` or `tier0 uns search --path-prefix Plant/Line1 --size 50 --json` | | UNS | Read current values | `tier0 uns read Plant/Line1/Metric/Temperature --json` or `tier0 uns read --topic 'Plant/+/Metric/Temperature' --json` | | UNS | Write values | `tier0 uns write --topic Plant/Line1/Metric/Temperature --value '{"temperature":27.5}'` | | UNS | Query history | `tier0 uns history -t Plant/Line1/Metric/Temperature --start -1h --json` | | UNS | Create paths or topics | `tier0 uns create --topic Plant/Line1 --type path` or `tier0 uns create --file namespace.json --json` | | UNS | Update metadata or fields | `tier0 uns update --path Plant/Line1/Metric/Temperature --description "Line 1 temperature" --update-mask description` | | UNS | Delete or restore nodes | `tier0 uns delete --path Plant/Line1/Metric/Temperature --yes` or `tier0 uns restore --path Plant/Line1/Metric/Temperature --yes` | | Node-RED Flow | List or inspect Flows | `tier0 flow list`, `tier0 flow list --source --json`, or `tier0 flow get --id 1 --json` | | Node-RED Flow | Create Source Flow or Event Flow | `tier0 flow create --name "modbus-collector" --source --desc "Modbus TCP collector"` or `tier0 flow create --name "alert-handler" --event --desc "Temperature alarm processor"` | | Node-RED Flow | Update Flow metadata | `tier0 flow update --id 1 --name "line1-collector"` or `tier0 flow update --id 1 --favorite` | | Node-RED Flow | Export canvas JSON | `tier0 flow data --id 1 --out flows.json` | | Node-RED Flow | Deploy canvas JSON | `tier0 flow deploy --id 1 -f flows.json --yes` | | Node-RED Flow | Delete Flows | `tier0 flow delete --id 1 --yes` | | Node-RED Flow | Check available node types | `tier0 flow nodes --source --json` or `tier0 flow nodes --event --json` | Let an agent read the repo first For complex UNS or Flow work, ask your agent to read the [Tier0 Skill repo](https://github.com/FREEZONEX/Tier0-skill) before running commands. The repo includes task-specific rules, examples, and safety notes for operations such as batch UNS writes and Node-RED Flow deploys. ## Uninstalling Tier0 Skills [Section titled “Uninstalling Tier0 Skills”](#uninstalling-tier0-skills) ```bash # Remove the Tier0 CLI while keeping local configuration. npx @tier0/cli@latest uninstall # Remove the Tier0 CLI and delete local configuration, credentials, skills, and cache. npx @tier0/cli@latest uninstall --purge ``` ## Next [Section titled “Next”](#next) * [Working with UNS Data](/using-tier0/working-with-uns-data/) - How to operate on your UNS model through MQTT and APIs. # Analyze UNS Data > Query live namespace data in Tier0 Notebook and build interactive analyses and machine-learning apps. Available in Cloud and Enterprise. Tier0 adopts Marimo Notebook for advanced data analysis with Python. ## Creating Notebooks [Section titled “Creating Notebooks”](#creating-notebooks) 1. Log in to Tier0, and select **Notebook**. 2. Hover over the **+**, click **New Notebook**. 3. Enter the notebook **Name** and click **Create**. 4. Click and enter the created notebook to start editing. ## Querying Data from UNS [Section titled “Querying Data from UNS”](#querying-data-from-uns) Know Before you start Data stored in database from UNS acts as an existing data source in **Notebook**. 1. Inside the notebook, click the **VARIABLES** on the left side. 2. Expand the **PostgreSQL** list, select a table under **tenant\_id** > **uns**. 3. Hover over the table, and click the **+** icon next to the table name to generate SQL statements cell automatically. ```sql SELECT * FROM "uns"."table_name" LIMIT 100 ``` 4. Click the **Run** icon at the lower-right corner to run all cells and get the query result. ## Analyzing UNS Data [Section titled “Analyzing UNS Data”](#analyzing-uns-data) ### Example Background [Section titled “Example Background”](#example-background) * Data source is temperature, pressure, status and vibration of a compressor collected from a factory and sent to Tier0 UNS. * Isolation Forest is used to recognize abnormal data points in the dataset. * Detection sensitivity can be adjusted, and the results are visualized accordingly in a dashboard. ### Operations [Section titled “Operations”](#operations) 1. Create a notebook and access it. 2. Add the following cells in order. Cell 1: Import dependencies. ```python import marimo as mo import pandas as pd import numpy as np import altair as alt from sklearn.preprocessing import StandardScaler from sklearn.ensemble import IsolationForest ``` Cell 2: Get data from UNS, clean it to get the analysis dataset. ```sql status_pd = status_data.to_pandas() temperature_pd = temperature_data.to_pandas() pressure_pd = pressure_data.to_pandas() vibration_pd = vibration_data.to_pandas() # Convert and sort timestamps for frame in [ status_pd, temperature_pd, pressure_pd, vibration_pd, ]: frame["_timestamp"] = pd.to_datetime( frame["_timestamp"], utc=True, errors="coerce", ) frame.dropna( subset=["_timestamp"], inplace=True, ) frame.sort_values( "_timestamp", inplace=True, ) # Use temperature as the main timeline sensor_data = pd.merge_asof( temperature_pd, pressure_pd, on="_timestamp", direction="nearest", tolerance=pd.Timedelta(seconds=30), ) sensor_data = pd.merge_asof( sensor_data, vibration_pd, on="_timestamp", direction="nearest", tolerance=pd.Timedelta(seconds=30), ) sensor_data = pd.merge_asof( sensor_data, status_pd, on="_timestamp", direction="nearest", tolerance=pd.Timedelta(seconds=30), ) # Remove incomplete records complete_data = sensor_data.dropna( subset=[ "temperature", "pressure", "vibration", "status_code", ] ) # Analyze only active running periods running_data = ( complete_data[ complete_data["status_code"] == 1 ] .reset_index(drop=True) ) running_data ``` Cell 3: Set the slider for anomaly detection threshold. ```python anomaly_threshold = mo.ui.slider( start=-0.15, stop=0.10, step=0.01, value=0.00, label="Anomaly detection threshold", ) mo.vstack([ mo.md("## Model Settings"), mo.md( """ Adjust the anomaly detection threshold. A higher threshold applies stricter criteria and identifies more operating records as anomalies. """ ), anomaly_threshold, ]) ``` Cell 4: Use the **Isolation Forest** model to detect anomalies. ```python feature_names = [ "temperature", "pressure", "vibration", ] # Prepare model input model_input = running_data[ feature_names ] # Standardize sensor values feature_scaler = StandardScaler() scaled_features = feature_scaler.fit_transform( model_input ) # Train the anomaly detection model anomaly_model = IsolationForest( n_estimators=200, contamination="auto", random_state=42, ) anomaly_model.fit( scaled_features ) # Calculate continuous anomaly scores anomaly_scores = anomaly_model.decision_function( scaled_features ) # Prepare analysis results ml_result = running_data.copy() ml_result["anomaly_score"] = anomaly_scores # Classify records using the interactive threshold ml_result["condition"] = np.where( ml_result["anomaly_score"] < anomaly_threshold.value, "Anomaly", "Normal", ) ml_result ``` Cell 5: Displays results on a dashboard. ```python # Calculate summary metrics dashboard_total = len(ml_result) dashboard_normal = int( ( ml_result["condition"] == "Normal" ).sum() ) dashboard_anomaly = int( ( ml_result["condition"] == "Anomaly" ).sum() ) dashboard_anomaly_ratio = ( dashboard_anomaly / dashboard_total * 100 if dashboard_total > 0 else 0 ) # Calculate anomaly severity anomaly_records = ml_result[ ml_result["condition"] == "Anomaly" ] if len(anomaly_records) > 0: average_anomaly_severity = float( ( anomaly_threshold.value - anomaly_records["anomaly_score"] ) .clip(lower=0) .mean() ) else: average_anomaly_severity = 0.0 # Illustrative equipment health score severity_penalty = min( average_anomaly_severity * 80, 20 ) dashboard_health_score = max( 0, 100 - dashboard_anomaly_ratio * 1.2 - severity_penalty ) # Determine risk level if dashboard_health_score < 60: dashboard_risk = "High" dashboard_conclusion = ( "The compressor shows a high level of unusual operating behavior. " "Further equipment inspection is recommended." ) elif dashboard_health_score < 80: dashboard_risk = "Medium" dashboard_conclusion = ( "Some unusual operating patterns were detected. " "Continue monitoring the equipment and review the abnormal periods." ) else: dashboard_risk = "Low" dashboard_conclusion = ( "Most operating records follow the learned normal pattern. " "No significant abnormal behavior was detected." ) # Header dashboard_header = mo.Html( """
Air Compressor Anomaly Detection
Machine learning analysis based on temperature, pressure, and vibration during active running periods.
""" ) # Conclusion card dashboard_conclusion_card = mo.Html( f"""
Analysis Summary
{dashboard_conclusion}
""" ) # KPI cards dashboard_kpis = mo.Html( f"""
HEALTH SCORE
{dashboard_health_score:.1f} / 100
RISK LEVEL
{dashboard_risk}
ANALYZED SAMPLES
{dashboard_total:,}
ANOMALY RATIO
{dashboard_anomaly_ratio:.1f}%
""" ) # Interactive anomaly chart dashboard_chart = ( alt.Chart(ml_result) .mark_circle( size=58, opacity=0.78, ) .encode( x=alt.X( "_timestamp:T", title="Time", ), y=alt.Y( "vibration:Q", title="Vibration RMS (mm/s)", ), color=alt.Color( "condition:N", title=None, scale=alt.Scale( domain=[ "Normal", "Anomaly", ], range=[ "#94a3b8", "#ef4444", ], ), ), tooltip=[ alt.Tooltip( "_timestamp:T", title="Time", ), alt.Tooltip( "temperature:Q", title="Temperature", format=".2f", ), alt.Tooltip( "pressure:Q", title="Pressure", format=".4f", ), alt.Tooltip( "vibration:Q", title="Vibration", format=".3f", ), alt.Tooltip( "anomaly_score:Q", title="Anomaly Score", format=".3f", ), alt.Tooltip( "condition:N", title="Condition", ), ], ) .properties( width=980, height=380, title=alt.TitleParams( text="Detected Operating Anomalies", subtitle=( f"Threshold: {anomaly_threshold.value:.2f} · " f"{dashboard_anomaly:,} anomalies detected" ), anchor="start", fontSize=17, fontWeight=600, subtitleFontSize=12, subtitleColor="#6b7280", offset=14, ), ) .configure_view( stroke=None ) .interactive() ) # Final dashboard mo.vstack([ dashboard_header, dashboard_conclusion_card, dashboard_kpis, mo.ui.altair_chart( dashboard_chart ), ]) ``` 3. Click **Run all stale cells** at the lower-right corner to run all cells and view the results. Different ways to view the results Change the view style at the upper-right corner among **Vertical**, **Grid** and **Slides**. ## Next [Section titled “Next”](#next) * [Build Apps on UNS](/using-tier0/build-apps/) — Build industrial applications with UNS data. * [UNS Concepts](/using-tier0/uns-concepts/) — Understand the data model you just analyzed. # Build Apps on UNS > A container is an app — deploy full containers on Tier0 Edge and Enterprise; build logic with Event Flow. ## What is a Tier0 App [Section titled “What is a Tier0 App”](#what-is-a-tier0-app) A Tier0 application has the following features: * It is a **Docker Container**. * It is generated from the conversation you have with the **App Builder**, zero-coding, no programming required. * It feeds on industrial data already in the **UNS**, and can *write back* to the **UNS**. * It relies on **Event Flow** to implement logic, which means you have control over the small tweaks and refinements. ## How to Deploy a Tier0 App [Section titled “How to Deploy a Tier0 App”](#how-to-deploy-a-tier0-app) ### Building a Tier0 App [Section titled “Building a Tier0 App”](#building-a-tier0-app) 1. Log in to Tier0, and select **App** > **Projects**. 2. Click **Create Project**, enter the project name and description, select an industry and click **Create**. 3. Open the project, click **Create App**, and you will be redirected to the application building page. 4. Delete the sample prompt and write the application requirements in the dialog. What to be included in the prompt Make sure you state the following information in the prompt: * Use the UNS model *\[uns\_model\_path]* as the data source and *\[uns\_model\_path]* as write back destination. * (optional) Create UNS model and flows in Source Flow to connect data sources. * Create flows in Event Flow to trigger anomaly alarms/low health scores/other events. 5. Start building the application, change as you need by talking to the LLM upon preview. 6. Click **Deploy** at the upper-right corner, enter changelog and then click **Submit**. ### Deploying on Tier0 Enterprise [Section titled “Deploying on Tier0 Enterprise”](#deploying-on-tier0-enterprise) 1. Go to **Projects**, find the application inside your project. 2. Access the application, click the **…** icon of the **Active** version, and select **Download ZIP**. 3. Log in to Tier0 Enterprise, and go to **Project**. 4. Click **Create Project**, enter a name and click **Save**. 5. Access the project, and click **Import App**, select and upload the ZIP package downloaded from Cloud. 6. Click **Save**, access the application and use it on Enterprise. ## Next [Section titled “Next”](#next) * [Analyze UNS Data](/using-tier0/analyze-data/) — Analyze UNS data with Marimo Notebook and Python. * [Best practice: connecting industrial protocols](/best-practice/protocol-connections/) # Connect Data to UNS > Model your namespace and bring data in with Source Flows. Available in all editions. ## How to Build a Data Model [Section titled “How to Build a Data Model”](#how-to-build-a-data-model) Based on simple folder-file structure, you can define the data hierarchy to a tree map. ### Building Models Manually [Section titled “Building Models Manually”](#building-models-manually) Note **factory/equipment/CNC** will be used as an example, in which `factory` and `equipment` are paths and `CNC` is a topic. 1. Log in to Tier0, select **UNS**. 2. Hover over the upper-right corner icon ![](http://communityimage2.oss-cn-hangzhou.aliyuncs.com/75.png) > **New Path** to add a path `factory`. 3. Enter the path name `factory`, click **Create**. 4. Select `factory`, hover over the same icon and add another path `equipment` under it. 5. Select `equipment`, hover over the same icon > **New Topic**, and add a topic `CNC` under it. 6. Set the topic **Name**, **Type**, select **Enable History**/**Mock Data**\*, click the arrow to continue. 7. Set the payload fields, click **Create** to finish the topic creation. Different selections When the **Topic Type** is set to **State** or **Action**: * **Mock Data**: Select it to generate mock data flow for testing. * **Enable History**: Select it to store the data in Tier0 database. ### Importing Models [Section titled “Importing Models”](#importing-models) * By JSON text 1. On the **UNS** page, click the upper-right corner icon **Import**. 2. Select the **JSON** tab, wirte JSON text on the window. 3. Click **Submit** to import the model. * By JSON file 1. On the **Import** window, select the **File** tab. 2. Click **Download Template** at the upper-right corner to download a JSON template. 3. Enter the model content in the template and save it. 4. Upload the file on the **Import** window, and click **Submit** to complete. Live template for easier import You can manually add a path and topic, export it and use it as an example for import. ## How to Connect Data to UNS [Section titled “How to Connect Data to UNS”](#how-to-connect-data-to-uns) 1. Log in to Tier0, go to **Flows**. 2. Click **New Flow** at the upper-right corner. 3. Enter a name, set the **Flow Type** to **Source Flow**, and click **Create**. 4. Click the flow you just added, drag in nodes according to your data source type (e.g.`modbus`) on the canvas. 5. Drag in an `mqtt out` node, select the **Server** as **emqx**. 6. Enter the topic name copies from **UNS** (e.g. `factory/equipment/CNC`), click **Done**. 7. Click **Deploy** at the upper-right corner, trigger the flow and the data will be sent to corresponding model under **UNS**. ## Addtional Options [Section titled “Addtional Options”](#addtional-options) Note This part states addtional parameters or configurations related to the workflow. | Scope | Parameter | Item | When to use | | ----- | ------------------------------------ | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | Path | Extended Attribute/Custom Attributes | - | Use when you need to add additional attributes to the path, such as unit information. | | Topic | Topic Type | [Metric, State, Action](/using-tier0/uns-concepts/#metric-state-action) | Select the topic type that matches the data semantics: measurements, current status, or executable operations. | | Topic | Attribute Generation Method | Pre-defined | Manually set attributes for the topic one by one. | | Topic | Attribute Generation Method | Auto-Parsing | Generates attributes automatically from JSON text in batches. | ## Next [Section titled “Next”](#next) * [Build Apps on UNS](/using-tier0/build-apps/) — Build industrial applications with UNS data. * [Analyze UNS Data](/using-tier0/analyze-data/) — Analyze UNS data with Marimo Notebook and Python. # UNS Concepts in Tier0 > The Unified Namespace model — Metric, State, Action, and how the UNS stores data. Data collected to Tier0 UNS is categorized into 3 types: **Metric**, **State**, and **Action**. Different types correspond to different storage methods. ## Metric, State, Action [Section titled “Metric, State, Action”](#metric-state-action) | Topic type | Carries | Example | | ---------- | ------------------ | ------------------------------------ | | `METRIC` | Real-time data | Machine temperature/pressure/voltage | | `STATE` | Status data | Machine operational status | | `ACTION` | Commands or events | Start batch, stop machine | ``` flowchart TD plant["Plant/"] --> line1["Line1/"] line1 --> metric["Metric/"] line1 --> state["State/"] line1 --> action["Action/"] metric --> temp(["Temperature"]) state --> status(["MachineStatus"]) action --> start(["StartBatch"]) class temp,status,start t0accent ``` ## How the UNS stores data [Section titled “How the UNS stores data”](#how-the-uns-stores-data) | Data Type | Storage | Description | | --------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | Metric | Time-series database | Optimized for high-frequency measurements such as temperature and pressure. Each topic includes default fields: `timestamp` and `quality`. | | State | PostgreSQL (JSONB) | Stores machine or system states in a flexible JSON format. | | Action | PostgreSQL (JSONB) | Stores commands or actions in a flexible JSON format. | ## Next [Section titled “Next”](#next) * [Connect Data to UNS](/using-tier0/connect-data/) — How to model your plant to UNS and connect data to the model. * [Working with UNS Data](/using-tier0/working-with-uns-data/) — How to operate on your UNS model through MQTT and APIs. # Working with UNS Data > Operate the UNS over MQTT and the API — with the API reference. Tier0 allows you to operate on the UNS through MQTT and APIs. ## MQTT [Section titled “MQTT”](#mqtt) Tier0 enables MQTT clients to connect to the internal MQTT broker and operate on the UNS. Note You can use MQTT clients such as **MQTTX**, **MQTT Explorer**, and **Node-RED** to connect to the broker and publish/subscribe messages. ### Generating MQTT Credentials [Section titled “Generating MQTT Credentials”](#generating-mqtt-credentials) 1. Log in to Tier0, select **Edge**. 2. Click **New Credentials** on the right side. 3. Enter the **Name** and **Description**, and click **Create**. Tip * Make a note of the generated **Client ID**, **Username**, and **Password**. You will need them to configure the MQTT client. * The following clients are used as examples to show how to use the credentials to connect to the internal MQTT broker. ### Connecting MQTT Clients [Section titled “Connecting MQTT Clients”](#connecting-mqtt-clients) 1. Install an MQTT client, such as MQTTX, MQTT Explorer and others. 2. Use the credentials from Tier0 to connect the client. * MQTTX 1. Install and open [MQTTX](https://mqttx.app/). 2. Click the **+** button at the upper-left corner to add a connection. 3. Enter the broker information and the generated credentials. ```json { "Host": "mqtt.tier0.dev", "Port": 8883, "Client ID": "", "Username": "", "Password": "" } ``` 4. Click **Connect**. * MQTT Explorer 1. Install and open [MQTT Explorer](https://mqtt-explorer.com/). 2. Click **Connections** at the upper-left corner to add a new connection. 3. Enter the broker information and the generated credentials. ```json { "Protocol": "mqtt://", "Host": "mqtt.tier0.dev", "Port": 8883, "Username": "", "Password": "" } ``` 4. Click **ADVANCED**, change the client ID to the generated one. 5. Click **BACK** and then **CONNECT**. * Node-RED 1. Install and open your [Node-RED](https://nodered.org/docs/getting-started/local) instance, add a new flow. 2. Drag in an `mqtt in` node and double-click to set its properties under the **Connection** tab. 3. Add an MQTT broker, enter `mqtt://mqtt.tier0.dev` as the **Server** and `1883` as the **Port**. 4. Enter the the generated ID next to **Client ID**. 5. Switch to **Security** tab, enter the generated **Username** and **Password**. 6. Click **Done**. ### Testing Connections [Section titled “Testing Connections”](#testing-connections) 1. Log in to Tier0, and go to **Edge**. 2. Click the **Clients** tab and check the status of each client. 3. Copy the topic of the data model under **UNS**, and publish a message to it through the client. 4. Go back to **UNS**, check the data under the target topic. ## API [Section titled “API”](#api) Use **RestAPI** to operate on UNS data, flows and authentication. How to use the API * The OpenAPI base path is `/openapi/v1`. Set `TIER0_BASE_URL` to your Tier0 host plus the base path, for example `https://your-workspace.tier0.app/openapi/v1`, and pass your API key in the `x-api-key` header. * API keys are generated under **Settings > API Keys** by clicking the account icon at the upper-rigt corner. - `/flow/create` Create a SourceFlow or EventFlow. `flowName` and `flowType` are required. * JavaScript ```js const BASE_URL = process.env.TIER0_BASE_URL; const API_KEY = process.env.TIER0_API_KEY; const response = await fetch(`${BASE_URL}/flow/create`, { method: 'POST', headers: { 'content-type': 'application/json', 'x-api-key': API_KEY, }, body: JSON.stringify({ flowName: 'Manufacturing source flow', flowType: 'SourceFlow', description: 'Collects UNS source data from the plant.', template: '{}', }), }); const result = await response.json(); console.log(result.data.id, result.data.brokerID); ``` * Python ```python import os import requests base_url = os.environ["TIER0_BASE_URL"] api_key = os.environ["TIER0_API_KEY"] response = requests.post( f"{base_url}/flow/create", headers={"x-api-key": api_key}, json={ "flowName": "Manufacturing source flow", "flowType": "SourceFlow", "description": "Collects UNS source data from the plant.", "template": "{}", }, timeout=30, ) response.raise_for_status() result = response.json() print(result["data"]["id"], result["data"]["brokerID"]) ``` * cURL ```bash curl -X POST "$TIER0_BASE_URL/flow/create" \ -H "content-type: application/json" \ -H "x-api-key: $TIER0_API_KEY" \ -d '{ "flowName": "Manufacturing source flow", "flowType": "SourceFlow", "description": "Collects UNS source data from the plant.", "template": "{}" }' ``` - `/flow/list` List flows and optionally filter by `flowType` or `keyword`. Use this to find the flow `id` before update or get operations. * JavaScript ```js const BASE_URL = process.env.TIER0_BASE_URL; const API_KEY = process.env.TIER0_API_KEY; const response = await fetch(`${BASE_URL}/flow/list`, { method: 'POST', headers: { 'content-type': 'application/json', 'x-api-key': API_KEY, }, body: JSON.stringify({ flowType: 'SourceFlow', keyword: 'Manufacturing', }), }); const result = await response.json(); console.log(result.data.list); ``` * Python ```python import os import requests base_url = os.environ["TIER0_BASE_URL"] api_key = os.environ["TIER0_API_KEY"] response = requests.post( f"{base_url}/flow/list", headers={"x-api-key": api_key}, json={ "flowType": "SourceFlow", "keyword": "Manufacturing", }, timeout=30, ) response.raise_for_status() result = response.json() print(result["data"]["list"]) ``` * cURL ```bash curl -X POST "$TIER0_BASE_URL/flow/list" \ -H "content-type: application/json" \ -H "x-api-key: $TIER0_API_KEY" \ -d '{ "flowType": "SourceFlow", "keyword": "Manufacturing" }' ``` - `/flow/update` Update a flow by `id`. Send only the fields you want to change. * JavaScript ```js const BASE_URL = process.env.TIER0_BASE_URL; const API_KEY = process.env.TIER0_API_KEY; const flowId = 123; const response = await fetch(`${BASE_URL}/flow/update`, { method: 'POST', headers: { 'content-type': 'application/json', 'x-api-key': API_KEY, }, body: JSON.stringify({ id: flowId, flowName: 'Manufacturing source flow v2', description: 'Updated source flow description.', isFavorite: 1, }), }); const result = await response.json(); console.log(result.data.success); ``` * Python ```python import os import requests base_url = os.environ["TIER0_BASE_URL"] api_key = os.environ["TIER0_API_KEY"] flow_id = 123 response = requests.post( f"{base_url}/flow/update", headers={"x-api-key": api_key}, json={ "id": flow_id, "flowName": "Manufacturing source flow v2", "description": "Updated source flow description.", "isFavorite": 1, }, timeout=30, ) response.raise_for_status() result = response.json() print(result["data"]["success"]) ``` * cURL ```bash curl -X POST "$TIER0_BASE_URL/flow/update" \ -H "content-type: application/json" \ -H "x-api-key: $TIER0_API_KEY" \ -d '{ "id": 123, "flowName": "Manufacturing source flow v2", "description": "Updated source flow description.", "isFavorite": 1 }' ``` - `/flow/get` Get one flow by `id`. * JavaScript ```js const BASE_URL = process.env.TIER0_BASE_URL; const API_KEY = process.env.TIER0_API_KEY; const flowId = 123; const response = await fetch(`${BASE_URL}/flow/get`, { method: 'POST', headers: { 'content-type': 'application/json', 'x-api-key': API_KEY, }, body: JSON.stringify({ id: flowId }), }); const result = await response.json(); console.log(result.data); ``` * Python ```python import os import requests base_url = os.environ["TIER0_BASE_URL"] api_key = os.environ["TIER0_API_KEY"] flow_id = 123 response = requests.post( f"{base_url}/flow/get", headers={"x-api-key": api_key}, json={"id": flow_id}, timeout=30, ) response.raise_for_status() result = response.json() print(result["data"]) ``` * cURL ```bash curl -X POST "$TIER0_BASE_URL/flow/get" \ -H "content-type: application/json" \ -H "x-api-key: $TIER0_API_KEY" \ -d '{"id": 123}' ``` ## API reference [Section titled “API reference”](#api-reference) * [Tier0 OpenAPI Reference](/reference/tier0-openapi/) ## Next [Section titled “Next”](#next) * [Build Apps on UNS](/using-tier0/build-apps/) — Build industrial applications with UNS data. * [Analyze UNS Data](/using-tier0/analyze-data/) — Analyze UNS data with Marimo Notebook and Python.