{"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"},"servers":[{"url":"https://marketplace.moveworks.com/api/purple-suite/dex"}],"security":[{"patAuth":[]}],"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}}}}},"paths":{"/devices":{"get":{"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":{"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"}}}}}}},"/devices/{id}":{"get":{"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":{"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":{"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"}}}},"/software_inventory":{"get":{"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":{"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"}}}}}}},"/software_inventory/{id}":{"get":{"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":{"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":{"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"}}}},"/dex_alerts":{"get":{"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":{"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"}}}}}}},"/dex_alerts/{id}":{"get":{"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":{"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":{"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"}}}},"/dex_alerts/{id}/acknowledge":{"post":{"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"}}}},"/dex_alerts/{id}/resolve":{"post":{"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"}}}},"/remediations":{"get":{"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":{"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"}}}}}}},"/remediations/{id}":{"get":{"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":{"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":{"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"}}}},"/remediations/{id}/execute":{"post":{"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"}}}},"/remediations/{id}/complete":{"post":{"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)"}}}},"/experience_scores":{"get":{"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":{"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"}}}}}}},"/experience_scores/{id}":{"get":{"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":{"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":{"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"}}}},"/dex_campaigns":{"get":{"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":{"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"}}}}}}},"/dex_campaigns/{id}":{"get":{"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":{"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":{"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"}}}},"/dex_campaigns/{id}/launch":{"post":{"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"}}}}}}