Purple Suite DEX API OpenAPI

Digital Employee Experience (DEX) API — monitor device health, software inventory, experience scores, alerts, remediations, and IT campaigns. Resembles tools like Nexthink, Lakeside, and ServiceNow DEX. --- **Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`.

This reference is readable without JavaScript. Download the OpenAPI document.

Runtime

/api/purple-suite/dex

Digital Employee Experience (DEX) API — monitor device health, software inventory, experience scores, alerts, remediations, and IT campaigns. Resembles tools like Nexthink, Lakeside, and ServiceNow DEX. --- **Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`.

Servers

[
  {
    "url": "https://marketplace.moveworks.com/api/purple-suite/dex"
  }
]

Authentication schemes

{
  "patAuth": {
    "type": "http",
    "scheme": "bearer"
  }
}

Each operation lists its security requirements. An empty security array permits an unauthenticated request. Use the scheme and headers declared for the selected contract.

Operations

GET /devices

List DEX-monitored endpoint devices with health telemetry

Managed endpoints (laptop, desktop, mobile, virtual_machine) and their live telemetry: health_score, CPU/memory/disk usage, battery health, uptime, last_seen, compliance_status, encryption and antivirus state, plus assigned_to, department and location. This is device-health monitoring (Nexthink/Lakeside style) — the finance-owned asset register lives in the erp app's assets collection, and user-reported device tickets live in itsm. Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key).

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List DEX-monitored endpoint devices with health telemetry",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Device"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true,
                "description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
              },
              "total": {
                "type": "integer",
                "description": "Total documents in the collection."
              },
              "@odata.count": {
                "type": "integer",
                "description": "Present only when $count=true: total matches after $filter/$search."
              },
              "@odata.truncated": {
                "type": "boolean",
                "description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
              }
            }
          }
        }
      }
    },
    "400": {
      "description": "Invalid OData query (e.g. malformed $filter)"
    }
  },
  "description": "Managed endpoints (laptop, desktop, mobile, virtual_machine) and their live telemetry: health_score, CPU/memory/disk usage, battery health, uptime, last_seen, compliance_status, encryption and antivirus state, plus assigned_to, department and location. This is device-health monitoring (Nexthink/Lakeside style) — the finance-owned asset register lives in the erp app's assets collection, and user-reported device tickets live in itsm. Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key)."
}

POST /devices

Create device

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Create device",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Device"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Device"
          }
        }
      }
    }
  }
}

GET /devices/{id}

Get device

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get device",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Device"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

PATCH /devices/{id}

Update device

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update device",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Device"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Device"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

DELETE /devices/{id}

Delete device

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete device",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

GET /software_inventory

List software installed on DEX-monitored devices

One row per software title installed on a device (device_id plus software_name, version, publisher) with install_date, last_used, license_type and the is_managed / is_approved flags. Use it for licence-usage, shadow-IT (is_approved false) and version-spread questions — software purchasing and licence assets live in the erp app. Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key).

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List software installed on DEX-monitored devices",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/SoftwareInventory"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true,
                "description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
              },
              "total": {
                "type": "integer",
                "description": "Total documents in the collection."
              },
              "@odata.count": {
                "type": "integer",
                "description": "Present only when $count=true: total matches after $filter/$search."
              },
              "@odata.truncated": {
                "type": "boolean",
                "description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
              }
            }
          }
        }
      }
    },
    "400": {
      "description": "Invalid OData query (e.g. malformed $filter)"
    }
  },
  "description": "One row per software title installed on a device (device_id plus software_name, version, publisher) with install_date, last_used, license_type and the is_managed / is_approved flags. Use it for licence-usage, shadow-IT (is_approved false) and version-spread questions — software purchasing and licence assets live in the erp app. Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key)."
}

POST /software_inventory

Create software inventory entry

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Create software inventory entry",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/SoftwareInventory"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/SoftwareInventory"
          }
        }
      }
    }
  }
}

GET /software_inventory/{id}

Get software inventory entry

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get software inventory entry",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/SoftwareInventory"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

PATCH /software_inventory/{id}

Update software inventory entry

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update software inventory entry",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/SoftwareInventory"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/SoftwareInventory"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

DELETE /software_inventory/{id}

Delete software inventory entry

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete software inventory entry",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

GET /dex_alerts

List DEX device-health alerts (machine-generated telemetry alerts)

Automated endpoint alerts — high_cpu, low_disk, low_memory, crash, network_issue, battery_critical, security_threat, compliance_violation, software_error, hardware_failure — with severity, open/acknowledged/resolved status, and the metric_value that breached its threshold. These are generated from device telemetry, not raised by users (user-reported tickets live in the itsm app); the remediations collection records what was done about them. Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key).

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List DEX device-health alerts (machine-generated telemetry alerts)",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/DexAlert"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true,
                "description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
              },
              "total": {
                "type": "integer",
                "description": "Total documents in the collection."
              },
              "@odata.count": {
                "type": "integer",
                "description": "Present only when $count=true: total matches after $filter/$search."
              },
              "@odata.truncated": {
                "type": "boolean",
                "description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
              }
            }
          }
        }
      }
    },
    "400": {
      "description": "Invalid OData query (e.g. malformed $filter)"
    }
  },
  "description": "Automated endpoint alerts — high_cpu, low_disk, low_memory, crash, network_issue, battery_critical, security_threat, compliance_violation, software_error, hardware_failure — with severity, open/acknowledged/resolved status, and the metric_value that breached its threshold. These are generated from device telemetry, not raised by users (user-reported tickets live in the itsm app); the remediations collection records what was done about them. Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key)."
}

POST /dex_alerts

Create DEX alert

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Create DEX alert",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/DexAlert"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/DexAlert"
          }
        }
      }
    }
  }
}

GET /dex_alerts/{id}

Get DEX alert

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get DEX alert",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/DexAlert"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

PATCH /dex_alerts/{id}

Update DEX alert

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update DEX alert",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/DexAlert"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/DexAlert"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

DELETE /dex_alerts/{id}

Delete DEX alert

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete DEX alert",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

POST /dex_alerts/{id}/acknowledge

Acknowledge a DEX alert

Marks the alert as acknowledged. Fails if the alert is already resolved or already acknowledged.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Acknowledge a DEX alert",
  "description": "Marks the alert as acknowledged. Fails if the alert is already resolved or already acknowledged.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "acknowledged_by": {
              "type": "string",
              "description": "Email of the person acknowledging the alert",
              "example": "admin@acme.com"
            }
          },
          "required": [
            "acknowledged_by"
          ]
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Alert acknowledged",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/DexAlert"
          }
        }
      }
    },
    "404": {
      "description": "Alert not found"
    },
    "422": {
      "description": "Alert already resolved or already acknowledged"
    }
  }
}

POST /dex_alerts/{id}/resolve

Resolve a DEX alert

Marks the alert as resolved. Fails if the alert is already resolved.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Resolve a DEX alert",
  "description": "Marks the alert as resolved. Fails if the alert is already resolved.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "resolved_by": {
              "type": "string",
              "description": "Email of the person resolving the alert",
              "example": "admin@acme.com"
            },
            "resolution_notes": {
              "type": "string",
              "description": "Optional notes about the resolution",
              "example": "Restarted service, CPU normalized."
            }
          },
          "required": [
            "resolved_by"
          ]
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Alert resolved",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/DexAlert"
          }
        }
      }
    },
    "404": {
      "description": "Alert not found"
    },
    "422": {
      "description": "Alert already resolved"
    }
  }
}

GET /remediations

List DEX remediation actions run against devices

Automated fix-it actions targeting a device (restart_service, clear_cache, force_update, restart_device, run_diagnostics, reset_network, update_drivers, scan_malware, enforce_compliance…) with status pending/in_progress/completed/failed, executor, timings and result. Use it to see what has already been attempted for a device or alert; the execute and complete tools are what drive the status. Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key).

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List DEX remediation actions run against devices",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Remediation"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true,
                "description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
              },
              "total": {
                "type": "integer",
                "description": "Total documents in the collection."
              },
              "@odata.count": {
                "type": "integer",
                "description": "Present only when $count=true: total matches after $filter/$search."
              },
              "@odata.truncated": {
                "type": "boolean",
                "description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
              }
            }
          }
        }
      }
    },
    "400": {
      "description": "Invalid OData query (e.g. malformed $filter)"
    }
  },
  "description": "Automated fix-it actions targeting a device (restart_service, clear_cache, force_update, restart_device, run_diagnostics, reset_network, update_drivers, scan_malware, enforce_compliance…) with status pending/in_progress/completed/failed, executor, timings and result. Use it to see what has already been attempted for a device or alert; the execute and complete tools are what drive the status. Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key)."
}

POST /remediations

Create remediation

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Create remediation",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Remediation"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Remediation"
          }
        }
      }
    }
  }
}

GET /remediations/{id}

Get remediation

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get remediation",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Remediation"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

PATCH /remediations/{id}

Update remediation

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update remediation",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Remediation"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Remediation"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

DELETE /remediations/{id}

Delete remediation

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete remediation",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

POST /remediations/{id}/execute

Execute a remediation

Transitions the remediation to in_progress and records the executor. Fails if already completed or in_progress. Failed remediations may be re-executed.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Execute a remediation",
  "description": "Transitions the remediation to in_progress and records the executor. Fails if already completed or in_progress. Failed remediations may be re-executed.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "executed_by": {
              "type": "string",
              "description": "Email or identifier of the executor (defaults to 'system')",
              "example": "admin@acme.com"
            }
          }
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Remediation started (status = in_progress)",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Remediation"
          }
        }
      }
    },
    "404": {
      "description": "Remediation not found"
    },
    "422": {
      "description": "Remediation already completed or already in_progress"
    }
  }
}

POST /remediations/{id}/complete

Mark a remediation as complete

Transitions the remediation to completed. Remediation must be in_progress.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Mark a remediation as complete",
  "description": "Transitions the remediation to completed. Remediation must be in_progress.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "result": {
              "type": "string",
              "description": "Outcome description (defaults to 'success')",
              "example": "success"
            }
          }
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Remediation completed",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Remediation"
          }
        }
      }
    },
    "404": {
      "description": "Remediation not found"
    },
    "422": {
      "description": "Remediation is not in_progress (must execute first)"
    }
  }
}

GET /experience_scores

List DEX digital-experience scores (0–10) per device and user

Periodic scorecards that roll boot time, responsiveness, network, stability and battery into an overall_score for a device_id / user_email at a measured_at timestamp, alongside the raw boot_time_seconds, crash count and response-time inputs. Use it for experience trends and worst-experience rankings; point-in-time device telemetry lives on devices instead. Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key).

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List DEX digital-experience scores (0–10) per device and user",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ExperienceScore"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true,
                "description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
              },
              "total": {
                "type": "integer",
                "description": "Total documents in the collection."
              },
              "@odata.count": {
                "type": "integer",
                "description": "Present only when $count=true: total matches after $filter/$search."
              },
              "@odata.truncated": {
                "type": "boolean",
                "description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
              }
            }
          }
        }
      }
    },
    "400": {
      "description": "Invalid OData query (e.g. malformed $filter)"
    }
  },
  "description": "Periodic scorecards that roll boot time, responsiveness, network, stability and battery into an overall_score for a device_id / user_email at a measured_at timestamp, alongside the raw boot_time_seconds, crash count and response-time inputs. Use it for experience trends and worst-experience rankings; point-in-time device telemetry lives on devices instead. Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key)."
}

POST /experience_scores

Create experience score

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Create experience score",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/ExperienceScore"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/ExperienceScore"
          }
        }
      }
    }
  }
}

GET /experience_scores/{id}

Get experience score

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get experience score",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/ExperienceScore"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

PATCH /experience_scores/{id}

Update experience score

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update experience score",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/ExperienceScore"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/ExperienceScore"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

DELETE /experience_scores/{id}

Delete experience score

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete experience score",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

GET /dex_campaigns

List DEX IT campaigns (fleet patch, rollout and compliance sweeps)

Fleet-wide IT operations targeting a device cohort — patch_deployment, software_rollout, compliance_check, security_scan, driver_update, os_upgrade — with target_criteria, status draft/active/completed and total/completed/failed device counts. These are IT rollout campaigns, not marketing campaigns (see the crm app). Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key).

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "List DEX IT campaigns (fleet patch, rollout and compliance sweeps)",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "default": 50
      },
      "description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
    },
    {
      "in": "query",
      "name": "cursor",
      "schema": {
        "type": "string"
      },
      "description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
    },
    {
      "in": "query",
      "name": "$filter",
      "schema": {
        "type": "string"
      },
      "description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
    },
    {
      "in": "query",
      "name": "$select",
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
    },
    {
      "in": "query",
      "name": "$orderby",
      "schema": {
        "type": "string"
      },
      "description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
    },
    {
      "in": "query",
      "name": "$search",
      "schema": {
        "type": "string"
      },
      "description": "Free-text, case-insensitive substring match across the resource's searchable fields."
    },
    {
      "in": "query",
      "name": "$top",
      "schema": {
        "type": "integer"
      },
      "description": "Maximum number of items to return (page size)."
    },
    {
      "in": "query",
      "name": "$skip",
      "schema": {
        "type": "integer"
      },
      "description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
    },
    {
      "in": "query",
      "name": "$count",
      "schema": {
        "type": "boolean"
      },
      "description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
    }
  ],
  "responses": {
    "200": {
      "description": "Paginated list",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Campaign"
                }
              },
              "nextCursor": {
                "type": "string",
                "nullable": true,
                "description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
              },
              "total": {
                "type": "integer",
                "description": "Total documents in the collection."
              },
              "@odata.count": {
                "type": "integer",
                "description": "Present only when $count=true: total matches after $filter/$search."
              },
              "@odata.truncated": {
                "type": "boolean",
                "description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
              }
            }
          }
        }
      }
    },
    "400": {
      "description": "Invalid OData query (e.g. malformed $filter)"
    }
  },
  "description": "Fleet-wide IT operations targeting a device cohort — patch_deployment, software_rollout, compliance_check, security_scan, driver_update, os_upgrade — with target_criteria, status draft/active/completed and total/completed/failed device counts. These are IT rollout campaigns, not marketing campaigns (see the crm app). Supports server-side OData querying — _filter, _orderby, _select, _search, _top, _skip, _count (the spec names these $filter etc.; MCP clients see the _ form because $ is not a legal tool-input key)."
}

POST /dex_campaigns

Create DEX campaign

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Create DEX campaign",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Campaign"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Campaign"
          }
        }
      }
    }
  }
}

GET /dex_campaigns/{id}

Get DEX campaign

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Get DEX campaign",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Campaign"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

PATCH /dex_campaigns/{id}

Update DEX campaign

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Update DEX campaign",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "description": "Fields to update (partial)",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Campaign"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Campaign"
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

DELETE /dex_campaigns/{id}

Delete DEX campaign

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Delete DEX campaign",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Deleted",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "success": {
                "type": "boolean"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "Not found"
    }
  }
}

POST /dex_campaigns/{id}/launch

Launch a DEX campaign

Transitions the campaign to active and records launch metadata. Fails if already active or completed.

Authentication

[
  {
    "patAuth": []
  }
]

Request, responses and operation details

{
  "summary": "Launch a DEX campaign",
  "description": "Transitions the campaign to active and records launch metadata. Fails if already active or completed.",
  "parameters": [
    {
      "$ref": "#/components/parameters/InstanceId"
    },
    {
      "in": "path",
      "name": "id",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "properties": {
            "launched_by": {
              "type": "string",
              "description": "Email or identifier of the person launching the campaign (defaults to 'system')",
              "example": "admin@acme.com"
            }
          }
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Campaign launched (status = active)",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Campaign"
          }
        }
      }
    },
    "404": {
      "description": "Campaign not found"
    },
    "422": {
      "description": "Campaign already active or already completed"
    }
  }
}

Schemas and reusable components

securitySchemes

patAuth

{
  "type": "http",
  "scheme": "bearer"
}

parameters

InstanceId

{
  "in": "header",
  "name": "X-Instance-ID",
  "required": true,
  "schema": {
    "type": "string"
  }
}

ImpersonateUser

{
  "in": "header",
  "name": "X-PS-Impersonate-User",
  "required": false,
  "schema": {
    "type": "string",
    "format": "email"
  },
  "description": "Optional. Act as a specific user in the target instance — the value is that user's email (must exist in the instance). Resolution order for the effective user: (1) this header, if set; (2) the instance's configured default MCP user; (3) otherwise no user is bound. User-scoped paths (e.g. /users/{email}/…) also accept the literal \"me\", and Graph-style apps serve /me/… twins of those paths (Graph: /me ≡ /users/{signed-in-user}) — both resolve to whatever this header (or the default MCP user) points at. An explicit path email always wins over the header. The unified MCP server does not send this header — it relies on the per-instance default — so it is primarily for direct REST/HTTP callers."
}

schemas

Device

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true
    },
    "hostname": {
      "type": "string",
      "example": "eng-dell-001"
    },
    "device_type": {
      "type": "string",
      "enum": [
        "laptop",
        "desktop",
        "mobile",
        "virtual_machine"
      ],
      "example": "laptop"
    },
    "os": {
      "type": "string",
      "example": "macOS 15 Sequoia"
    },
    "os_version": {
      "type": "string",
      "example": "24.3.0"
    },
    "manufacturer": {
      "type": "string",
      "example": "Apple"
    },
    "model": {
      "type": "string",
      "example": "MacBook Pro 16-inch"
    },
    "serial_number": {
      "type": "string",
      "example": "SN-A1B2C3D4"
    },
    "assigned_to": {
      "type": "string",
      "description": "Email of the assigned user",
      "example": "alice@acme.com"
    },
    "department": {
      "type": "string",
      "example": "Engineering"
    },
    "location": {
      "type": "string",
      "example": "San Francisco HQ"
    },
    "ip_address": {
      "type": "string",
      "example": "10.0.1.42"
    },
    "last_seen": {
      "type": "string",
      "format": "date-time",
      "example": "2026-05-28T14:23:00.000Z"
    },
    "health_score": {
      "type": "number",
      "minimum": 0,
      "maximum": 100,
      "example": 87
    },
    "cpu_usage_pct": {
      "type": "number",
      "example": 34
    },
    "memory_usage_pct": {
      "type": "number",
      "example": 61
    },
    "disk_usage_pct": {
      "type": "number",
      "example": 45
    },
    "battery_health_pct": {
      "type": "number",
      "example": 92
    },
    "uptime_hours": {
      "type": "number",
      "example": 168
    },
    "compliance_status": {
      "type": "string",
      "enum": [
        "compliant",
        "non_compliant",
        "unknown"
      ],
      "example": "compliant"
    },
    "encryption_enabled": {
      "type": "boolean",
      "example": true
    },
    "antivirus_status": {
      "type": "string",
      "enum": [
        "active",
        "outdated",
        "disabled",
        "not_installed"
      ],
      "example": "active"
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

SoftwareInventory

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true
    },
    "device_id": {
      "type": "string",
      "example": "DEV-0001"
    },
    "software_name": {
      "type": "string",
      "example": "Microsoft 365"
    },
    "version": {
      "type": "string",
      "example": "16.4.0"
    },
    "publisher": {
      "type": "string",
      "example": "Microsoft"
    },
    "install_date": {
      "type": "string",
      "format": "date",
      "example": "2025-11-15"
    },
    "last_used": {
      "type": "string",
      "format": "date-time",
      "example": "2026-05-27T09:00:00.000Z"
    },
    "license_type": {
      "type": "string",
      "enum": [
        "licensed",
        "freeware",
        "open_source",
        "trial",
        "unknown"
      ],
      "example": "licensed"
    },
    "is_managed": {
      "type": "boolean",
      "example": true
    },
    "is_approved": {
      "type": "boolean",
      "example": true
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

DexAlert

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true
    },
    "device_id": {
      "type": "string",
      "example": "DEV-0003"
    },
    "alert_type": {
      "type": "string",
      "enum": [
        "high_cpu",
        "low_disk",
        "low_memory",
        "crash",
        "network_issue",
        "battery_critical",
        "security_threat",
        "compliance_violation",
        "software_error",
        "hardware_failure"
      ],
      "example": "high_cpu"
    },
    "severity": {
      "type": "string",
      "enum": [
        "critical",
        "high",
        "medium",
        "low",
        "info"
      ],
      "example": "high"
    },
    "status": {
      "type": "string",
      "enum": [
        "open",
        "acknowledged",
        "resolved"
      ],
      "example": "open"
    },
    "title": {
      "type": "string",
      "example": "CPU sustained above 95% for 30 min"
    },
    "description": {
      "type": "string",
      "example": "Automated alert triggered on endpoint device."
    },
    "metric_value": {
      "type": "number",
      "description": "The metric reading that triggered the alert",
      "example": 97
    },
    "threshold": {
      "type": "number",
      "description": "The threshold that was exceeded",
      "example": 90
    },
    "acknowledged": {
      "type": "boolean",
      "example": false
    },
    "acknowledged_by": {
      "type": "string",
      "example": "admin@acme.com"
    },
    "acknowledged_at": {
      "type": "string",
      "format": "date-time",
      "example": "2026-05-28T10:00:00.000Z"
    },
    "resolved_at": {
      "type": "string",
      "format": "date-time",
      "example": "2026-05-28T11:30:00.000Z"
    },
    "resolved_by": {
      "type": "string",
      "example": "admin@acme.com"
    },
    "resolution_notes": {
      "type": "string",
      "description": "Notes recorded when the alert was resolved",
      "example": "Restarted service, CPU normalized."
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

Remediation

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true
    },
    "device_id": {
      "type": "string",
      "example": "DEV-0003"
    },
    "action_type": {
      "type": "string",
      "enum": [
        "restart_service",
        "clear_cache",
        "force_update",
        "restart_device",
        "run_diagnostics",
        "repair_installation",
        "reset_network",
        "update_drivers",
        "scan_malware",
        "enforce_compliance"
      ],
      "example": "clear_cache"
    },
    "status": {
      "type": "string",
      "enum": [
        "pending",
        "in_progress",
        "completed",
        "failed",
        "cancelled"
      ],
      "example": "pending"
    },
    "target_software": {
      "type": "string",
      "description": "Optional: specific software targeted",
      "example": "Microsoft Teams"
    },
    "executed_by": {
      "type": "string",
      "example": "admin@acme.com"
    },
    "executed_at": {
      "type": "string",
      "format": "date-time",
      "example": "2026-05-28T12:00:00.000Z"
    },
    "completed_at": {
      "type": "string",
      "format": "date-time",
      "example": "2026-05-28T12:05:00.000Z"
    },
    "result": {
      "type": "string",
      "example": "success"
    },
    "notes": {
      "type": "string",
      "example": "Auto-triggered by alert"
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

ExperienceScore

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true
    },
    "device_id": {
      "type": "string",
      "example": "DEV-0001"
    },
    "user_email": {
      "type": "string",
      "example": "alice@acme.com"
    },
    "overall_score": {
      "type": "number",
      "minimum": 0,
      "maximum": 10,
      "example": 7.8
    },
    "boot_time_score": {
      "type": "number",
      "minimum": 0,
      "maximum": 10,
      "example": 8.2
    },
    "responsiveness_score": {
      "type": "number",
      "minimum": 0,
      "maximum": 10,
      "example": 7.5
    },
    "network_score": {
      "type": "number",
      "minimum": 0,
      "maximum": 10,
      "example": 8
    },
    "stability_score": {
      "type": "number",
      "minimum": 0,
      "maximum": 10,
      "example": 7.1
    },
    "battery_score": {
      "type": "number",
      "minimum": 0,
      "maximum": 10,
      "example": 8.6
    },
    "measured_at": {
      "type": "string",
      "format": "date-time",
      "example": "2026-05-29T06:00:00.000Z"
    },
    "boot_time_seconds": {
      "type": "number",
      "example": 28
    },
    "avg_app_crash_count": {
      "type": "number",
      "example": 1
    },
    "avg_response_time_ms": {
      "type": "number",
      "example": 320
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

Campaign

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "readOnly": true
    },
    "name": {
      "type": "string",
      "example": "Q2 2026 patch deployment"
    },
    "description": {
      "type": "string",
      "example": "Automated patch deployment campaign targeting managed devices."
    },
    "campaign_type": {
      "type": "string",
      "enum": [
        "patch_deployment",
        "software_rollout",
        "compliance_check",
        "security_scan",
        "driver_update",
        "os_upgrade"
      ],
      "example": "patch_deployment"
    },
    "status": {
      "type": "string",
      "enum": [
        "draft",
        "active",
        "paused",
        "completed",
        "cancelled"
      ],
      "example": "draft"
    },
    "target_criteria": {
      "type": "object",
      "description": "Filter criteria for targeted devices (e.g. OS, department)",
      "example": {
        "os": "Windows 11",
        "department": "Engineering"
      }
    },
    "total_devices": {
      "type": "integer",
      "example": 20
    },
    "completed_devices": {
      "type": "integer",
      "example": 0
    },
    "failed_devices": {
      "type": "integer",
      "example": 0
    },
    "launched_at": {
      "type": "string",
      "format": "date-time",
      "example": "2026-05-29T08:00:00.000Z"
    },
    "launched_by": {
      "type": "string",
      "example": "admin@acme.com"
    },
    "created_by": {
      "type": "string",
      "example": "admin@acme.com"
    },
    "createdAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time",
      "readOnly": true
    }
  }
}

Document metadata and extensions

{
  "openapi": "3.0.0",
  "info": {
    "title": "Purple Suite DEX API",
    "description": "Digital Employee Experience (DEX) API — monitor device health, software inventory, experience scores, alerts, remediations, and IT campaigns. Resembles tools like Nexthink, Lakeside, and ServiceNow DEX.\n\n---\n\n**Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`.",
    "version": "1.0.0"
  },
  "security": [
    {
      "patAuth": []
    }
  ]
}
Interactive API explorer (requires JavaScript)