# Litmus API - Chain-of-API Workflows

This document maps every multi-step API chain that the UI or SDK orchestrates behind a single user action.
Each section includes a step-by-step reference table showing the exact API calls, order, inputs, and outputs.

---

## Quick Reference: All Chain Workflows

| # | Workflow | UI Action / Trigger | Product | Steps | Protocol |
|---|----------|-------------------|---------|-------|----------|
| 1 | [Apply / Upload Template](#1-apply--upload-template-le) | System -> Device Mgmt -> Templates -> Apply | LE | 3 (Step 0-2) | REST |
| 2 | [Restore Backup](#2-restore-backup-le) | System -> Device Mgmt -> Backup -> Restore | LE | 3 (Step 0-2) | REST |
| 3 | [Upload Custom CA Certificate](#3-upload-custom-ca-certificate-le) | System -> Network -> Certificates -> Upload CA | LE | 2 (Step 1-2) | REST |
| 4 | [Upload Analytics / TensorFlow Model](#4-upload-analytics--tensorflow-model-le) | Analytics -> Models -> Add Model | LE | 3 (Step 1-3) | REST |
| 5 | [Upload AI/ML Model to LEM](#5-upload-aiml-model-to-lem) | LEM -> AI/ML Models -> Upload New | LEM | 3 (Step 1-3) | REST + S3 |
| 6 | [Create DeviceHub Device + Tags](#6-create-devicehub-device--tags) | DeviceHub -> Add Device -> Add Tag | LE | 5 steps | GraphQL |
| 7 | [DeviceHub Browse (Bulk Tag Discovery)](#7-devicehub-browse-bulk-tag-discovery) | DeviceHub -> Browse -> Create Tags from Browse | LE | 7 steps | GraphQL |
| 8 | [Deploy Edge Application](#8-deploy-edge-application) | Applications -> Marketplace -> Launch | LE | 4 steps | REST |
| 9 | [Configure OPC UA Server with Tags](#9-configure-opc-ua-server-with-tags) | OPC UA -> Import Tags -> Start Server | LE | 4 steps | GraphQL |
| 10 | [Create Integration Instance](#10-create-integration-instance) | Integration -> Add Connector | LE | 4 steps | GraphQL |

---

## Pattern: Resumable Upload Sessions

Workflows 1-4 all share the same **resumable upload** pattern used throughout the LE REST API:

| Phase | Method | What it does | Key output |
|-------|--------|-------------|-----------|
| **Clear** (Step 0) | `DELETE` | Remove any incomplete/stale upload session | - |
| **Create Session** (Step 1) | `POST` | Declare file size, get a session ID back | `{id: "..."}` |
| **Upload** (Step 2/3) | `PUT` | Stream the actual file bytes to the session endpoint | 200 OK |

> **Why DELETE first?** If a previous upload was interrupted (network drop, browser close), the session persists on the server. Deleting it first ensures a clean state and prevents conflicts.

---

## 1. Apply / Upload Template (LE)

**UI trigger**: System -> Device Management -> Templates -> *Apply Template* (downloads a `.zip`, then re-uploads it)

> **What the UI does invisibly**: When a user clicks "Download Template", the UI first clears any previous upload session, then creates a new one, then uploads the file - the user just sees a file download/upload dialog.

### Step table

| Step | Name in Collection | Method | Endpoint | Body / Notes | Output |
|------|-------------------|--------|----------|-------------|--------|
| 0 | Step 0: Apply Template | `DELETE` | `{{edgeUrl}}/dm/template/v2` | No body. Clears any stale upload session. | 200 OK |
| 1 | Step 1: Apply Template | `POST` | `{{edgeUrl}}/dm/template/v2` | `{"size": <file_bytes>}` | `{"id": "<session_id>"}` |
| 2 | Step 2: Apply Template | `PUT` | `{{edgeUrl}}/dm/template/v2/{id_from_step1}/resume` | Binary file bytes in body | 200 OK, template applied |

**Also relevant** - to *download* a template first (before applying it elsewhere):

| Name in Collection | Method | Endpoint | Notes |
|-------------------|--------|----------|-------|
| Download Template | `POST` | `{{edgeUrl}}/dm/template` | Body: JSON with device IDs and flags to include/exclude components |


---

## 2. Restore Backup (LE)

**UI trigger**: System -> Device Management -> Backup/Restore -> *Apply Backup*

### Step table

| Step | Name in Collection | Method | Endpoint | Body / Notes | Output |
|------|-------------------|--------|----------|-------------|--------|
| 0 | Step 0: Apply Backup - Delete Upload Sessions | `DELETE` | `{{edgeUrl}}/dm/backup/v2` | No body. Clears stale sessions. | 200 OK |
| 1 | Step 1: Apply Backup - Create Upload Session | `POST` | `{{edgeUrl}}/dm/backup/v2` | `{"size": <backup_file_bytes>}` | `{"id": "<session_id>"}` |
| 2 | Step 2: Apply Backup - Resume/Apply Backup | `PUT` | `{{edgeUrl}}/dm/backup/v2/{id_from_step_1}/resume` | Binary backup file bytes | 200 OK, device restores |


---

## 3. Upload Custom CA Certificate (LE)

**UI trigger**: System -> Network -> Certificates -> *Upload Custom CA Certificate*

> No Step 0 here - the certificate store doesn't require clearing existing sessions first.

### Step table

| Step | Name in Collection | Method | Endpoint | Body / Notes | Output |
|------|-------------------|--------|----------|-------------|--------|
| 1 | Step 1: Upload Custom CA Certificate | `POST` | `{{edgeUrl}}/dm/certstore` | `{"size": <cert_bytes>}` | `{"id": "<upload_id>"}` |
| 2 | Step 2: Upload Custom Certificate | `PUT` | `{{edgeUrl}}/dm/certstore/{step1_upload_id}` | Binary `.pem` / `.crt` file bytes | 200 OK, certificate installed |


---

## 4. Upload Analytics / TensorFlow Model (LE)

**UI trigger**: Analytics -> Models -> *Add AI Model*

> Step 1 here is a DELETE (same clear-first pattern as Template/Backup), but named "Step 1" instead of "Step 0".

### Step table

| Step | Name in Collection | Method | Endpoint | Body / Notes | Output |
|------|-------------------|--------|----------|-------------|--------|
| 1 | Step 1: Upload TensorFlow Model | `DELETE` | `{{edgeUrl}}/analytics/v2/upload_model/v2` | No body. Clears existing model upload session. | 200 OK |
| 2 | Step 2: Upload TensorFlow Model | `POST` | `{{edgeUrl}}/analytics/v2/upload_model/v2` | `{"size": <model_bytes>, "name": "<model_name>.zip"}` | `{"id": "<upload_id>"}` |
| 3 | Step 3: Upload TensorFlow Model | `PUT` | `{{edgeUrl}}/analytics/v2/upload_model/v2/resume/{{TensorFlow_model_upload_id}}` | Binary `.zip` model file bytes | 200 OK, model stored |


---

## 5. Upload AI/ML Model to LEM

**UI trigger**: LEM -> Edge Lifecycle Management -> AI/ML Models -> *Upload New Model*

> LEM uses a **pre-signed S3 URL** pattern instead of the LE resumable-session pattern. Step 1 gets a signed URL; Step 2 uploads directly to object storage; Step 3 registers the model in LEM.

### Step table

| Step | Name in Collection | Method | Endpoint | Body / Notes | Output |
|------|-------------------|--------|----------|-------------|--------|
| 1 | Step 1 of Upload New AI/ML Model | `POST` | `{{LEM_URL}}/api/v1/file/{{project_id}}/mlModel/{{filename}}/upload-url` | `{"projectId": "...", "fileName": "model.zip"}` | `{"url": "<signed_s3_url>"}` |
| 2 | Step 2 of Upload New AI/ML Model | `PUT` | `{signed_s3_url from Step 1}` | Binary `.zip` model file bytes. URL contains AWS SigV4 query params. | 200 OK (S3 direct upload) |
| 3 | Step 3 of Upload New Model | `POST` | `{{LEM_URL}}/api/v1/file/{{project_id}}/mlModel/{{filename}}` | `{"description": "..."}` - commits/registers the model in LEM | Model available in LEM |

**Auth note**: Steps 1 and 3 use `X-AuthToken: {{LEM_AdminApiToken}}`. Step 2 (S3 PUT) uses the embedded AWS SigV4 signature in the URL - **no additional auth header needed**.


---

## 6. Create DeviceHub Device + Tags

**UI trigger**: DeviceHub -> Add Device -> (configure) -> Add Tag

This is the most common chain customers struggle with. Each sub-step feeds data into the next.

### Step table

| Step | API Name | Method | Endpoint / GraphQL Operation | Input | Output / What to capture |
|------|----------|--------|------------------------------|-------|--------------------------|
| 1 | List of Drivers | `POST` | `{{edgeUrl}}/devicehub/v2` -> `ListDrivers` | No input required | `DriverID`, `Name` per protocol (e.g. Modbus TCP, OPC UA, etc.) |
| 2 | Get Driver Template by DriverID | `POST` | `{{edgeUrl}}/devicehub/v2` -> `GetDriver` | `{ID: "<DriverID from step 1>"}` | `DeviceProperties` - required fields/tabs for device config |
| 3 | Create New Device | `POST` | `{{edgeUrl}}/devicehub/v2` -> `CreateDevice` | Driver ID + device properties from step 2 | `DeviceID` |
| 4 | Get Driver Supported Registers | `POST` | `{{edgeUrl}}/devicehub/v2` -> `GetDriver` with `SupportedRegisters` | `{ID: "<DriverID>"}` | `SupportedRegisters[].Name`, `ReadOnly`, `RegisterProperties` - valid register types and value types for this driver |
| 5 | Create Tag | `POST` | `{{edgeUrl}}/devicehub/v2` -> `CreateRegisters` | `DeviceID` (step 3) + register type (step 4) + tag properties | Tag ID, tag is live |

> **Why step 4 is critical**: Each driver supports a different set of register types (e.g. Modbus TCP supports `Holding Register`, `Input Register`, `Coil`, `Discrete Input`). Calling `CreateRegisters` with an unsupported type returns a validation error. Always check `SupportedRegisters` before building the `CreateRegisters` payload.

> **Optional pre-validation**: Use `ValidateUpdateRegisters` (GraphQL query) to dry-run a tag payload before actually calling `CreateRegisters`. The query returns `Errors[]` without committing anything.


---

## 7. DeviceHub Browse (Bulk Tag Discovery)

**UI trigger**: DeviceHub -> Browse -> Select tags -> Create All

The Browse workflow auto-discovers a device's full address space (e.g. all OPC UA nodes or Modbus registers) and lets users select which ones to import as tags - without manually entering each one.

### Step table

| Step | API Name | Method | Endpoint / GraphQL Operation | Input | Output / What to capture |
|------|----------|--------|------------------------------|-------|--------------------------|
| 1 | Create Browse Task | `POST` | `{{edgeUrl}}/devicehub/v2` -> `CreateBrowseTask` | Device ID + browse params | Browse Task `ID` |
| 2 | Start Browse Task | `POST` | `{{edgeUrl}}/devicehub/v2` -> `StartBrowseTasks` | Browse Task `ID` from step 1 | Task starts crawling |
| 3 | List Browse Tasks *(poll)* | `POST` | `{{edgeUrl}}/devicehub/v2` -> `ListBrowseTasks` | - | Poll until `Status = Completed` |
| 4 | Get Browse Task Folders | `POST` | `{{edgeUrl}}/devicehub/v2` -> `BrowseTask_GetFoldersForTreeView` | Browse Task `ID` | Folder tree of discovered address space |
| 5 | Get Browse Folder Items | `POST` | `{{edgeUrl}}/devicehub/v2` -> `BrowseTask_GetFolderItems` | Folder path within the browse task | Items (tags/nodes) in that folder |
| 6 | Cart Add Items | `POST` | `{{edgeUrl}}/devicehub/v2` -> `Cart_AddItems` | Selected item IDs from step 5 | Items added to cart (`Added`, `Skipped`, `Updated` counts) |
| 6b | Cart Update Properties *(optional)* | `POST` | `{{edgeUrl}}/devicehub/v2` -> `Cart_UpdateProperties` | Cart item IDs + property overrides | Updated counts |
| 7 | Cart Create All Registers | `POST` | `{{edgeUrl}}/devicehub/v2` -> `Cart_CreateAllRegisters` | No input - processes entire cart | `Created`, `Skipped`, `Updated` counts - tags now exist |

> **Also useful**: `Download Browse Tags` (`GET /devicehub/browsing/download_csv/task/{task_id}`) - exports discovered tags to CSV before committing. Lets users review in Excel before creating.


---

## 8. Deploy Edge Application

**UI trigger**: Applications -> Marketplace -> *Launch*

### Step table

| Step | API Name | Method | Endpoint | Notes | Output |
|------|----------|--------|----------|-------|--------|
| 1 | List Marketplace Catalogs | `GET` | `{{edgeUrl}}/apps/...` | Check which marketplace sources are configured | Catalog list |
| 2 | Get Marketplace Apps | `GET` | `{{edgeUrl}}/apps/...` | Browse available apps in a catalog | App list + `appID` |
| 3 | Launch a Catalog App | `POST` | `{{edgeUrl}}/apps/...` | Supply `appID`, config/env vars | App installed + started |
| 4 | Get Running Catalog Apps | `GET` | `{{edgeUrl}}/apps/...` | Confirm app is running | Status = `running` |

> For app lifecycle after launch: `Start` (PUT), `Stop` (PUT), `Uninstall` (DELETE) - all in Applications -> Catalog Apps.


---

## 9. Configure OPC UA Server with Tags

**UI trigger**: OPC UA -> Import Tags -> Start Server

### Step table

| Step | API Name | Method | Endpoint / GraphQL Operation | Notes | Output |
|------|----------|--------|------------------------------|-------|--------|
| 1 | Get Node Root | `POST` | `{{edgeUrl}}/opcua/v2` -> `GetNodeRoot` | See current OPC UA hierarchy | Existing node tree |
| 2a | Import ALL DH Tags | `POST` | `{{edgeUrl}}/opcua/v2` -> `ImportAllDHTags` | Imports every DeviceHub tag into OPC UA | Nodes created |
| 2b | Import Some DH Tags | `POST` | `{{edgeUrl}}/opcua/v2` -> `ImportSomeDeviceHubTags` | Selective import by device or tag IDs | Nodes created |
| 3 | Start OPC UA Service | `POST` | `{{edgeUrl}}/opcua/v2` -> `StartOPCUAService` | Starts the OPC UA server | Server running |
| 4 | Get Service Configuration | `POST` | `{{edgeUrl}}/opcua/v2` -> `GetServiceConfiguration` | Verify endpoint URL, auth settings | Config object |


---

## 10. Create Integration Instance

**UI trigger**: Integration -> Add Connector -> Select Provider -> Configure -> Save

The Integration module (internally `cc`) connects Litmus Edge to cloud services and databases. Creating an instance requires knowing the provider's config schema first, then supplying the correct fields in a CreateInstance mutation.

### Step table

| Step | API Name | Method | Endpoint / GraphQL Operation | Input | Output / What to capture |
|------|----------|--------|------------------------------|-------|--------------------------|
| 1 | Get Provider Schema | `POST` | `{{edgeUrl}}/cc/v2` -> `GetProviderSchema` | Provider ID | Full config schema (field names, types, required, defaults, enums). See **Provider Schemas - Reference** folder for all 45 providers. |
| 2 | Create Instance | `POST` | `{{edgeUrl}}/cc/v2` -> `CreateInstance` | Provider ID + Config JSON (matching schema from step 1) | `InstanceID`, `ProviderID`, `Online`, `Status`. See **Create Instance - Provider Examples** folder for all 45 providers. |
| 3 | List Instances | `POST` | `{{edgeUrl}}/cc/v2` -> `ListInstances` | Optional filter by InstanceID | Verify instance was created, check `Online` and `Status` |
| 4 | Create Subscription (topics) | `POST` | `{{edgeUrl}}/cc/v2` -> `CreateSubscription` | Instance ID + topic config | Topic subscription created - data starts flowing |

> **Reference folders**: The collection includes **Provider Schemas - Reference** (45 entries, one per provider with full GetProviderSchema response) and **Create Instance - Provider Examples** (45 entries, one per provider with CreateInstance mutation + sample config). Use these to look up the exact config fields for any provider.

> **Supported providers** (45): AMQP (SSL/TCP), Aveva Data Hub, OSI PI Historian, AWS IoT Core/SiteWise, Azure Event Hubs/IoT Hub, Cognite, Databricks SQL, Google Cloud Pub/Sub, IBM Watson, InfluxDB (v1.7/v2), Kafka (SSL/TCP, Gen 2), Litmus UNS, MQTT (Generic/v5/SSL), MongoDB (v4+), NATS, Oracle Fusion Cloud, REST API, SQL Server, MySQL, PostgreSQL, Snowflake, Splunk.
