---
name: datto-edr
description: "Control the ACG Datto EDR / Datto AV tenant (azcomp4587): fleet orgs/sites/agents, MITRE-tagged detections, per-client security sweeps; gated scans, host isolation, agent deploy. Read-only default. Triggers: datto edr, datto av, infocyte, EDR detections, isolate endpoint."
---

# Datto EDR (Infocyte HUNT) Skill

Standalone CLI for the Datto EDR REST API against the live ACG tenant
(`azcomp4587.infocyte.com`). Datto EDR is the rebranded **Infocyte HUNT** platform,
so the API is a per-tenant **LoopBack** service: `https://<instance>.infocyte.com/api/<Model>`.
Read-only by default; mutating operations are gated behind `--confirm`.

One API token sees the **entire MSP fleet** (13 client orgs, 40 scan groups, 215
agents, 1500+ alerts as of build) — segmented per-client by Organization.

## Running the CLI

```bash
PY="bash $CLAUDETOOLS_ROOT/.claude/scripts/py.sh"
EDR="$PY C:/claudetools/.claude/skills/datto-edr/scripts/edr.py"

$EDR status                      # tenant rollup
$EDR orgs                        # clients (Organizations)
$EDR sites --org <orgId>         # client sites (Locations)
$EDR agents --org <orgId>        # endpoints for a client
$EDR detections --org <orgId> --days 7
$EDR sweep                       # per-client posture, sorted by recent detections
```

Transport auto-selects httpx if installed, else stdlib urllib (no hard dependency).

## Credentials

The token is NEVER hardcoded. Loaded at runtime from the SOPS vault:

```
bash "$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh" \
     get-field msp-tools/datto-edr.sops.yaml credentials.api_token
```

**Auth model (critical):** the token is passed in the **raw `Authorization` header
— NOT `Bearer <token>`, no prefix.** (Verified live 2026-06-25.) For testing you can
override with `DATTO_EDR_TOKEN` and `DATTO_EDR_BASE_URL`. The token **expires ~1 year
after creation (~2027-06-25)** — refresh it in the console (username menu → Admin →
Users & Tokens → API Tokens) and update the vault entry before it lapses.

## Data model (how the tenant is structured)

- **Organization** = a client/company (`orgs`). Carries agentCount/alertCount.
- **Location** = a client site (`sites`). Carries `organizationId`. **Agents belong
  to a Location** via `Agent.locationId`. This is the inventory hierarchy:
  Organization → Location → Agent.
- **Target** = a *scan* target group (`scan-targets`), the scannable unit for the
  `scan` command (`POST targets/{id}/scan`). Distinct from Location (though they may
  share an id when a site maps 1:1 to a scan group).
- **deviceGroup** = a global category ("Servers", "Workstations") spanning all orgs —
  NOT a per-client grouping. Not used for client scoping.
- **Alert** = a detection. Carries `severity` (0 info … 4 critical), `mitreTactic`/
  `mitreId`, `hostname`, `organizationName`, `targetGroupName`, `eventTime`.

## Command surface

### Reads (run freely — always live, never cached)

```bash
$EDR status                                  # counts: orgs/sites/agents/active/isolated/alerts
$EDR orgs                                    # clients + agent/alert/site counts
$EDR sites [--org <orgId>]                   # Locations (client sites)
$EDR scan-targets [--org <orgId>]            # scan target groups (for `scan`)
$EDR agents [--org <orgId>] [--site <locationId>] [--limit N]
$EDR agent <agentId>                         # full agent detail
$EDR detections [--org <orgId>] [--site <locationId>] [--severity 0-4] [--days N] [--limit N]
$EDR detection <alertId>                     # full alert detail
$EDR sweep [--org <orgId>]                   # per-client posture rollup (headline view)
$EDR extensions                              # list response/collection extensions
$EDR tasks [--type "Scan - EDR"] [--limit N] # recent userTasks (scan/analysis jobs)
$EDR task <taskId>                           # one userTask (scan job) detail/status
$EDR deploy-cmd --regkey <key> | --group <targetId>   # emit the agent install one-liner (see Deployment)
```

`--json` is a global flag (place before the subcommand): `$EDR --json sweep`.

### Mutating (gated — refuse without `--confirm`)

Scan/isolate go through `POST Agents/scan` with a **`where` filter selecting the
agents**. The CLI REQUIRES explicit `--agent` id(s): an absent `where` scans the
ENTIRE tenant (the "156-host" footgun), so the client refuses an empty list.

```bash
# EDR-scan specific agent(s). Repeat --agent for several. POST Agents/scan
#   {"where":{"and":[{"id":[ids]}]}, "options":{}, "taskName":"Scan - EDR"}
$EDR scan --agent <agentId> [--agent <agentId2> ...] --confirm

# Response extension (Host Isolation, etc.) on agent(s) — rides Agents/scan w/ options.extensions
$EDR isolate --agent <agentId> [--extension-name "Host Isolation [Win/Linux]"] --confirm
$EDR isolate --agent <agentId> --extension-id <extId> --confirm

# Cancel a running scan/task; create a group; mint a group registration key
$EDR cancel <taskId> --confirm
$EDR create-group --name "[TEST] Foo" --org <organizationId> --confirm
$EDR mint-key --group <targetId> --key <10char> --confirm

# Power tool — any endpoint. Non-GET requires --confirm.
$EDR raw GET Agents --filter '{"limit":1}'
$EDR raw POST Agents/scan --data '{"where":{"and":[{"id":["<id>"]}]},"options":{},"taskName":"Scan - EDR"}' --confirm
```

**The single-agent scan body that works:** `{"where":{"and":[{"id":["<agentId>"]}]},
"options":{}, "taskName":"Scan - EDR"}`. A bare `{"id":{"inq":[...]}}` returns HTTP
412 "column reference id is ambiguous" — the `and`-wrap is required. AV scans
(`Scan - AV Quick/Full`) are policy-driven, NOT callable via this endpoint.

## Deployment

Agent install is **not a REST call** — it's the agent binary run on the endpoint.
`deploy-cmd` emits the official one-liner. It needs an explicit key: either
`--regkey <key>` (used verbatim) or `--group <targetId>` (looks up THAT group's key
from `/agentKeys`). It will **not** auto-pick an arbitrary key — keys are
per-target-group and cross-client, so a wrong key lands the agent in the wrong org.
To land an agent in a SPECIFIC group: `create-group` → `mint-key` (the key `id` is
caller-supplied) → `deploy-cmd --group <targetId>` → install with that `-RegKey`.

```
Install-EDR -URL "https://azcomp4587.infocyte.com" -RegKey <key>
```

**Pass the full `-URL`, not `-InstanceName azcomp4587`** — the install script's loose
`.com` regex matches "zcom" inside the cname, leaving `--url` empty and the agent
erroring `a value is required for '--url'`. Push via **GuruRMM** (`/rmm`) or any
remote-exec channel. `RegKey` auto-approves + adds to the key's target group; without
it the agent registers but awaits manual approval.

**Offline endpoint whose GuruRMM agent is down but ScreenConnect is up:** the one-liner
is a single self-contained command, so queue it via the `screenconnect` skill's
`send-command --session <id> --command "powershell -NoProfile -ExecutionPolicy Bypass
-Command \"...Install-EDR...\"" --confirm` — SC holds it in the session's one command slot
and runs it on reconnect (verify by `agents --org` / `sweep` afterward; SC returns no
output). Used 2026-07-08 to stage REPAIRADMIN (IMC) while it was offline overnight.
**AV-swap ordering:** when replacing another AV (e.g. Bitdefender), install + verify EDR
live FIRST, then remove the old AV — never leave an unprotected window. Note Bitdefender's
API has NO uninstall (console-only); pull it via its local uninstall tool over `/rmm`.

## Safety gating

- Reads never mutate and run without confirmation.
- `scan`, `isolate`, `cancel`, `create-group`, `mint-key`, and any non-GET `raw` print
  a `[DRY-RUN]` line and exit non-zero unless `--confirm` is passed.
- `scan`/`isolate` additionally REFUSE an empty `--agent` list (would scan the whole
  tenant). **Never** scan/isolate casually — these hit live client production endpoints.
  Confirm the agent id with the read commands first.

## Verified vs. unverified

**Live-verified 2026-06-25 (exercised end-to-end on the tenant):** all reads
(`status`, `orgs`, `sites`, `scan-targets`, `agents` incl. per-org Location mapping,
`agent`, `detections`, `detection`, `sweep`, `extensions`, `tasks`, `task`); the full
deploy lifecycle (`create-group` → `mint-key` → install via `/rmm` → agent registered
into the group); `scan` (targeted single-agent — confirmed "Scanning 1 host"); `cancel`;
`deploy-cmd`; `raw`; and the `--confirm` + empty-`--agent` guards.

**RUN-unverified (shape correct, not fired at prod):** `isolate` — it rides the same
`Agents/scan` call with the Host Isolation extension in `options.extensions`; confirm
the extension id via `$EDR extensions` and test on an ACG-internal box before relying
on it (it cuts the endpoint off the network). AV scans are policy-driven (not callable).

## Relationship to GuruRMM (Feature 6)

This skill is the **prototype for GuruRMM security-connector #2** (Datto EDR), exactly
as the `bitdefender` skill prototyped connector #1. Its read logic (per-client agents +
detections + AV/isolation state) ports into the GuruRMM `SecurityVendor` trait; the
live "EDR add-on" dashboard is best fed by EDR **webhooks** (Admin → Webhooks) into a
GuruRMM receiver, complemented by REST polling here. See
`projects/msp-tools/guru-rmm/docs/RMM_THOUGHTS.md` Feature 6.

## Reference

Full endpoint map, auth detail, LoopBack filter syntax, and the scan/response payload
shapes: `references/api-reference.md`.
