syncro: expand skill to full Syncro API surface (assets/RMM/deploy) + verified capability model
Add comprehensive Syncro coverage beyond PSA core: - New .claude/standards/syncro/api-reference.md: complete verified inventory of ~180 endpoints across 38 resource types (generated from live OpenAPI 3.0 spec + tenant probe 2026-07-10), with worked GET/POST/PUT/DELETE templates and token-capability matrix. - /syncro: asset read intelligence (patches, installed_applications), asset create/update, policy-folder move (move-asset), RMM alerts, and deploy-agent (hybrid installer push via GuruRMM using SyncroSetup --console --allow-force-reboot). - move-asset ships a capability preflight (GET /policy_folders?customer_id -> 401 = missing Assets-Policy-Change) + mandatory post-write verify, because an under-scoped token returns HTTP 200 and silently no-ops the move. Correct the "Syncro RMM is API-impossible" belief: it was a token-scope gap, not an API limit. Live-verified the asset move (flip-and-restore 692253->692278->692253). Token scope today: Howard + Winter full; Mike (vaulted ...ebbeb3) still 401 pending re-vault. Corrects memory reference-syncro-rmm-api-gui-only; correction logged to errorlog.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,12 @@ Create, update, close, comment on, and bill tickets in Syncro PSA.
|
||||
/syncro estimate <customer> <subject> Create ticket + linked estimate with line items and private purchase notes
|
||||
/syncro schedules <customer> List recurring invoice schedules for a customer
|
||||
/syncro schedule <id> View a schedule's template and line items
|
||||
/syncro assets <customer> List a customer's RMM assets (read)
|
||||
/syncro asset <id> Asset detail + patches + installed applications
|
||||
/syncro asset-update <id> Rename / fix serial / edit an asset (preview-gated)
|
||||
/syncro move-asset <id> <folder> Move asset to a policy folder (needs token scope - see below)
|
||||
/syncro rmm-alerts [<customer>] List live RMM alerts; mute/clear by id
|
||||
/syncro deploy-agent <customer> <host> Push the Syncro RMM agent to a machine via GuruRMM
|
||||
```
|
||||
|
||||
## API Configuration
|
||||
@@ -26,6 +32,10 @@ Create, update, close, comment on, and bill tickets in Syncro PSA.
|
||||
**API Key:** per-user tokens in SOPS vault — see "Get API key" below
|
||||
**Rate limit:** 180 requests/minute per IP
|
||||
**Docs:** https://api-docs.syncromsp.com/
|
||||
**Full endpoint catalog:** `.claude/standards/syncro/api-reference.md` — every Syncro call
|
||||
(~180 endpoints, 38 resource types), verified against the live spec + tenant 2026-07-10.
|
||||
This file documents the core PSA + asset/RMM/agent-deploy workflows; the reference covers
|
||||
the rest (contacts, contracts, leads, vendors, POs, products, payments, wiki, policy folders).
|
||||
|
||||
## Hard Rules (violations have occurred — no exceptions)
|
||||
|
||||
@@ -599,26 +609,156 @@ COMMENT_ID=$(echo "$COMMENT_RESP" | jq -r '.comment.id')
|
||||
- Do NOT wrap the payload in `{"comment": {...}}` — returns 422.
|
||||
- **If `COMMENT_ID` is null:** GET `/tickets/{id}` and check `.ticket.comments[]` by subject before doing anything else. Comments cannot be deleted via API — duplicates require manual GUI removal.
|
||||
|
||||
#### Customer Assets (retire / archive — API GAP)
|
||||
#### Customer Assets (RMM) — read intelligence + write management
|
||||
|
||||
**Retiring a device in Syncro = "Archive" (UI ONLY — no REST API).** Verified against
|
||||
Syncro's own docs 2026-07-08 (`docs.syncrosecure.com/assets-rmm/archive-assets`): archiving
|
||||
is done in the web UI only — asset Details page → **Actions → Archive**, or bulk via the
|
||||
Assets table checkboxes → **Bulk Actions → Archive**. There is **no** archive endpoint and
|
||||
**no** `archived` field on `PUT /customer_assets/{id}`. Do NOT try to archive via API, and
|
||||
do NOT substitute `DELETE /customer_assets/{id}` — delete is destructive/irreversible and
|
||||
loses history + breaks ticket linkages (Archive keeps the asset visible on its tickets/alerts).
|
||||
Assets are Syncro's RMM device records. Reads are free; writes follow the standard
|
||||
preview+confirm gate. Response shape is `{"asset": {...}}` (single) / `{"assets": [...]}`
|
||||
(list). RMM telemetry lives under `.asset.properties.kabuto_information`.
|
||||
|
||||
- **To retire assets: hand the user the asset list + the Bulk-Actions-→-Archive steps.** We
|
||||
cannot do it for them via API.
|
||||
- Asset READS are fine: `GET /customer_assets?customer_id=N[&query=<name>]` (list, 100/page,
|
||||
`.assets[]`) and `GET /customer_assets/{id}` (detail; `.asset` — RMM data lives under
|
||||
`.asset.properties.kabuto_information`). `updated_at` is NOT a reliable "last online" (bulk
|
||||
account touches move it) — use the ScreenConnect `GuestInfoUpdateTime` or kabuto data for
|
||||
real last-seen.
|
||||
- **[RESEARCH]** Re-check if Syncro ships an archive API (watch release notes). If/when it
|
||||
does, wire it in here and add `--confirm` gating. Until then this is a "cannot be performed
|
||||
via skill" function. (First hit: IMC retirement pass 2026-07-08.)
|
||||
**Reads (verified with per-user token 2026-07-10):**
|
||||
|
||||
```bash
|
||||
# List / search a customer's assets (100/page, .assets[])
|
||||
curl -s "${BASE}/customer_assets?customer_id=${CUST_ID}&per_page=100&api_key=${API_KEY}" | \
|
||||
tr -d '\000-\037' | jq -r '.assets[] | "\(.id)\t\(.asset_type_name)\t\(.name)"'
|
||||
|
||||
# Asset detail
|
||||
curl -s "${BASE}/customer_assets/${ASSET_ID}?api_key=${API_KEY}" | tr -d '\000-\037' | \
|
||||
jq '{id:.asset.id, name:.asset.name, customer_id:.asset.customer_id, policy_folder_id:.asset.policy_folder_id, serial:.asset.asset_serial}'
|
||||
|
||||
# Windows patch posture -> {available_patches, available_patches_meta, installed_patches}
|
||||
curl -s "${BASE}/customer_assets/${ASSET_ID}/patches?api_key=${API_KEY}" | tr -d '\000-\037' | \
|
||||
jq '{available: (.available_patches|length), installed: (.installed_patches|length)}'
|
||||
|
||||
# Installed software inventory (paginated) -> {installed_applications, meta}
|
||||
# Use this to VERIFY an install (e.g. after /syncro deploy-agent) — see Agent Deployment.
|
||||
curl -s "${BASE}/customer_assets/${ASSET_ID}/installed_applications?per_page=100&api_key=${API_KEY}" | \
|
||||
tr -d '\000-\037' | jq -r '.installed_applications[]?.name' | sort -u
|
||||
```
|
||||
|
||||
`updated_at` is NOT a reliable "last online" (bulk account touches move it) — use the
|
||||
ScreenConnect `GuestInfoUpdateTime` or kabuto data for real last-seen.
|
||||
|
||||
**Writes — `POST /customer_assets` (create), `PUT /customer_assets/{id}` (update).**
|
||||
`name` is **required** on both (422 without it). Accepted body fields: `name`,
|
||||
`asset_serial`, `asset_type_id`, `asset_type_name`, `customer_id`, `properties`. Preview the
|
||||
full payload and confirm before writing; post a bot alert after. Response `{"asset": {...}}`.
|
||||
|
||||
```bash
|
||||
# Update (rename / fix serial). Always resend name (required) even when only changing serial.
|
||||
curl -s -X PUT "${BASE}/customer_assets/${ASSET_ID}?api_key=${API_KEY}" \
|
||||
-H "Content-Type: application/json" --data-binary @- <<JSON
|
||||
{"name": "${NEW_NAME}", "asset_serial": "${NEW_SERIAL}"}
|
||||
JSON
|
||||
```
|
||||
|
||||
**Move to a policy folder — `PUT /customer_assets/{id}` with `policy_folder_id` — WORKS, but
|
||||
is TOKEN-SCOPE-GATED and MUST be capability-checked + verified.** Live-proven 2026-07-10 via
|
||||
flip-and-restore on ACG-internal asset `12335235` (`692253 → 692278 → 692253`) with Howard's
|
||||
full-scope token. The spec: *"Updating only `policy_folder_id` requires **Assets - Policy
|
||||
Change**; with other fields requires **Assets - Edit** AND **Assets - Policy Change**. Nil,
|
||||
nonexistent, or cross-customer folder IDs return 422."* `name` is required on the PUT, so a
|
||||
move always sends other fields → needs BOTH scopes.
|
||||
|
||||
**The failure mode is SILENT, so never trust the 200.** A token WITHOUT Policy-Change returns
|
||||
`HTTP 200` and simply does not change the folder (this is the symptom once misread as "RMM is
|
||||
GUI-only"). `/me` reports coarse `asset:{write:true}` even without Policy-Change, so it cannot
|
||||
detect this — you must probe and verify:
|
||||
|
||||
```bash
|
||||
move_asset() { # $1=asset_id $2=target_folder_id (needs $BASE $API_KEY)
|
||||
local AID="$1" TARGET="$2" NAME CUST CUR
|
||||
local A; A=$(curl -s "${BASE}/customer_assets/${AID}?api_key=${API_KEY}" | tr -d '\000-\037')
|
||||
NAME=$(echo "$A" | jq -r '.asset.name'); CUST=$(echo "$A" | jq -r '.asset.customer_id')
|
||||
CUR=$(echo "$A" | jq -r '.asset.policy_folder_id')
|
||||
# 1) CAPABILITY PREFLIGHT — 200 = token can move; 401 = lacks Assets - Policy Change
|
||||
local PC; PC=$(curl -s -o /dev/null -w '%{http_code}' "${BASE}/policy_folders?customer_id=${CUST}&api_key=${API_KEY}")
|
||||
if [ "$PC" = "401" ]; then
|
||||
echo "[ERROR] This token lacks 'Assets - Policy Change' — cannot move policy folders."
|
||||
echo " Options: move it in the Syncro web console, OR have a user whose token has"
|
||||
echo " the scope run it (do NOT silently swap keys — writes attribute to the owner)."
|
||||
return 1
|
||||
fi
|
||||
# 2) PUT (name required), then 3) VERIFY the value actually changed — a 200 no-op = failure
|
||||
curl -s -X PUT "${BASE}/customer_assets/${AID}?api_key=${API_KEY}" -H "Content-Type: application/json" \
|
||||
--data-binary "$(jq -nc --arg n "$NAME" --argjson pf "$TARGET" '{name:$n, policy_folder_id:$pf}')" >/dev/null
|
||||
local NEW; NEW=$(curl -s "${BASE}/customer_assets/${AID}?api_key=${API_KEY}" | tr -d '\000-\037' | jq -r '.asset.policy_folder_id')
|
||||
if [ "$NEW" = "$TARGET" ]; then echo "[OK] asset ${AID} moved ${CUR} -> ${NEW}"; else
|
||||
echo "[ERROR] move NOT applied (still ${NEW}) — token likely under-scoped; do it in the console."; return 1; fi
|
||||
}
|
||||
```
|
||||
|
||||
**Policy-folder route quirks (verified 2026-07-10):** `GET /policy_folders` (bare) → **404**;
|
||||
`GET /policy_folders?customer_id=N` → **200** (list a customer's folders — how you enumerate
|
||||
folder IDs); `GET /policy_folders/{id}` → **401** even with full scope. Find valid same-customer
|
||||
folder IDs from the `?customer_id=N` list or from other assets' `policy_folder_id` values.
|
||||
|
||||
**Fleet posture:** Howard's per-user token now has full scope (verified). Other per-user tokens
|
||||
may not — the preflight above handles that per-run. Recommend granting every per-user Syncro
|
||||
token the Policy-Change scope so `move-asset` behaves the same regardless of who runs it (Mike's
|
||||
call — least-privilege alternative is to leave the preflight to degrade gracefully). Corrects
|
||||
memory `reference-syncro-rmm-api-gui-only`; full detail in `.claude/standards/syncro/api-reference.md`.
|
||||
|
||||
**Archive (retire) is still GUI-only — no REST API.** Verified 2026-07-08: archiving is web
|
||||
UI only (asset Details → **Actions → Archive**, or Assets table → **Bulk Actions →
|
||||
Archive**). There is no archive endpoint and no `archived` field on the PUT. Do NOT substitute
|
||||
`DELETE /customer_assets/{id}` — delete is destructive/irreversible, loses history, and breaks
|
||||
ticket linkages (Archive keeps the asset on its tickets/alerts). To retire: hand the user the
|
||||
asset list + the Bulk-Actions-→-Archive steps.
|
||||
|
||||
#### RMM Alerts
|
||||
|
||||
Live RMM alert feed (`GET /rmm_alerts` -> `{rmm_alerts, meta}`, verified 2026-07-10). Reads
|
||||
free; `POST /rmm_alerts/{id}/mute` and `DELETE /rmm_alerts/{id}` (clear) are gated writes.
|
||||
|
||||
```bash
|
||||
curl -s "${BASE}/rmm_alerts?per_page=50&api_key=${API_KEY}" | tr -d '\000-\037' | \
|
||||
jq -r '.rmm_alerts[] | "\(.id)\t\(.created_at)\t\(.description // .kind)"'
|
||||
# Mute: POST /rmm_alerts/${ALERT_ID}/mute | Clear: DELETE /rmm_alerts/${ALERT_ID}
|
||||
```
|
||||
|
||||
#### Agent Deployment (`/syncro deploy-agent`) — hybrid via GuruRMM
|
||||
|
||||
Push the Syncro RMM agent to a machine. Syncro's API has **no** installer-download endpoint,
|
||||
so this is a **hybrid**: the per-policy-folder installer (which embeds the customer enrollment
|
||||
token) is obtained once from the Syncro GUI and vaulted; **GuruRMM (`/rmm`)** does the actual
|
||||
push (it runs PowerShell as SYSTEM on the endpoint). `/rmm diagnose` already *detects* a
|
||||
Syncro agent as a competitor-RMM check — this is the deploy half.
|
||||
|
||||
**Prerequisites (one-time per customer):**
|
||||
1. In Syncro: **Assets & RMM → Downloads** (or the target Policy Folder) → copy the
|
||||
`SyncroSetup-<company>-<token>.exe` **download URL**. It is per-policy-folder — the token
|
||||
binds the installed agent to that customer + policy. Do NOT reuse one customer's URL for
|
||||
another.
|
||||
2. Vault it via the `vault` skill at `clients/<slug>/syncro-agent-installer.sops.yaml`
|
||||
(`credentials.installer_url`). Never hardcode the token URL.
|
||||
|
||||
**Deploy workflow:**
|
||||
1. Resolve the target host in GuruRMM (`/rmm` — resolve hostname → UUID; confirm `os_type`
|
||||
is `windows` and it is `is_connected`). Confirm the customer↔host mapping with the user.
|
||||
2. Preview the exact install command + target, get explicit confirmation.
|
||||
3. Dispatch via GuruRMM (`command_type: powershell`, SYSTEM context). The agent installs
|
||||
**silently by default**; `--console` runs it non-interactively and `--allow-force-reboot`
|
||||
permits the reboot Syncro triggers if it must update .NET first:
|
||||
|
||||
```powershell
|
||||
$url = '<vaulted SyncroSetup URL>'
|
||||
$exe = "$env:TEMP\SyncroSetup.exe"
|
||||
Invoke-WebRequest -Uri $url -OutFile $exe -UseBasicParsing
|
||||
Start-Process -FilePath $exe -ArgumentList '--console','--allow-force-reboot' -Wait
|
||||
```
|
||||
(Refs: Syncro deploy docs `docs.syncrosecure.com/agents-alerts-automations/deploy-an-endpoint`;
|
||||
community "Command Line install".) `--allow-force-reboot` CAN reboot the endpoint — treat
|
||||
deploy-agent as an outward-facing action: confirm reboot tolerance with the user first.
|
||||
4. **Verify** the agent enrolled: after ~2-3 min, either re-query GuruRMM output, or confirm
|
||||
the new asset appears under the customer via `GET /customer_assets?customer_id=N&query=<host>`
|
||||
and/or that "Syncro" shows in the endpoint's `installed_applications`.
|
||||
5. Bot alert (RMM write → `[RMM]`/`[DEPLOY]` routes to #dev-alerts; Syncro-side note to
|
||||
#bot-alerts if an asset record was created).
|
||||
|
||||
**[VERIFY before first production use]** Confirm `--console --allow-force-reboot` against the
|
||||
current Syncro installer on one ACG-internal test machine before rolling to a customer — the
|
||||
switches are stable historically but the installer is versioned.
|
||||
|
||||
#### Customers
|
||||
|
||||
|
||||
Reference in New Issue
Block a user