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
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
- [ACG Office Network Infrastructure](infra_office_network.md) — IPs/hosts/roles for pfSense/Jupiter/VMs/Docker. Check before assuming; .21 (Uranus) is storage.
|
||||
- [Power Failure Runbook](../POWER_FAILURE_RUNBOOK.md) — Recovery order after a power event: Tailscale routes, libvirt/VMs, Seafile, NPM/DNS.
|
||||
- [Syncro API — Invoice Verification Pattern](syncro_invoice_verification_pattern.md) — /invoices?customer_id=X returns no ticket linkage; query /invoices/{number} for ticket_id. Compare by ticket ID, not number.
|
||||
- [Syncro RMM policies = API-impossible](reference_syncro_rmm_api_gui_only.md) — policy create/assign/folder-move is GUI-ONLY; `policy_folder_id` is read-only on PUT (live-proven), policy endpoints 404, /policy_folders 401 scope-gated. Don't build /syncro move-asset; use `bitdefender` for API policy work.
|
||||
- [Syncro asset/RMM IS API-drivable](reference_syncro_rmm_api_gui_only.md) — CORRECTED 2026-07-10: asset read/create/update, patches, installed_apps, rmm_alerts all work via API; ONLY policy folders + `policy_folder_id` move need the `Assets - Policy Change` token scope (401 today). Archive stays GUI-only. Full catalog: `.claude/standards/syncro/api-reference.md`.
|
||||
- [Datto EDR detection behavior](reference_datto_edr_detection_behavior.md) — alert `sourceType`: `av`=Datto AV signature, `rule`=EDR reputation detection (both via `edr.py detections`). EDR is reputation-based not structural (wire known-bad file as autostart exe to trip it; loose files aren't surveyed). AV is tamper-protected (console-only disable); disabling Datto AV uninstalls it + Defender auto-reactivates (AMSI blocks scripts with literal EICAR → build from char codes). Verified live on RMM-TEST-MACHINE.
|
||||
- [Approval Workflow: Tools vs Projects](approval-workflow-tools-vs-projects.md) — Tools (remediation, scripts): Howard/Claude with approval. Projects (GuruRMM): Mike approval for architecture/features; Howard can handle merges/deploys himself (2026-06-21); bugs→bug list.
|
||||
- [CDP Chrome driver](reference_cdp_chrome_driver.md) — Drive Chrome via DevTools Protocol (.claude/scripts/cdp.py): visible window + screenshots-to-disk so Gemini/Grok can SEE the live site. Use localhost not 127.0.0.1; dedicated profile. Antigravity-style.
|
||||
|
||||
@@ -1,15 +1,41 @@
|
||||
---
|
||||
name: reference-syncro-rmm-api-gui-only
|
||||
description: Syncro's public API cannot manage RMM policies/folders — creation, assignment, and asset moves are GUI-only (live-verified 2026-06-25)
|
||||
description: Syncro asset/RMM IS API-drivable (reads, create/update, patches, installed_apps, rmm_alerts) — only policy-folder ops need a token scope our per-user keys currently lack (re-verified 2026-07-10)
|
||||
metadata:
|
||||
type: reference
|
||||
---
|
||||
|
||||
**Syncro RMM policy management is GUI-only — the public REST API does NOT expose it.** Live-verified against the ACG production tenant (computerguru.syncromsp.com) on 2026-06-25:
|
||||
**CORRECTION (2026-07-10): most of Syncro's asset/RMM surface IS API-drivable — the earlier
|
||||
"RMM is GUI-only" conclusion was a token-scope symptom, not an API limitation.** Live-probed
|
||||
the ACG production tenant with a per-user token (howard) on 2026-07-10 and confirmed against
|
||||
the OpenAPI 3.0 spec (`api-docs.syncromsp.com/swagger.json`):
|
||||
|
||||
- `GET /customer_assets` objects carry a read-only **`policy_folder_id`** field (which policy folder the machine sits in). **`PUT /customer_assets/{id}` with `policy_folder_id` is silently ignored** — returns HTTP 200 but the value never changes. Proven by a flip-and-restore test on ACG-internal asset 12335235 (DESKTOP-0O8A1RL): value stayed at folder 692253. **You CANNOT move a machine between policy folders via the API.**
|
||||
- `/policies`, `/policy_builders`, `/rmm_policies`, `/asset_policies` all return **404** — no policy-CRUD endpoints exist. Policy Builder (the `/policy_builders` GUI page) is web-console only.
|
||||
- `/policy_folders` (collection and specific-ID) returns **401** — the route exists but our API token lacks RMM/policy scope. A re-issued token *might* read folders, but since assets can't be moved anyway, it's moot for the move use case.
|
||||
- Syncro docs (docs.syncrosecure.com / docs.syncromsp.com "Work with Policies") confirm: policies are created in Policy Builder, assigned via an Organization's "Assets & Policies" subtab "Update Assigned Policy" dropdown, or "Bulk Assign Top-Level Policy" — all GUI, **no API mention**.
|
||||
- **Works with our current per-user token (HTTP 200):** `GET/POST /customer_assets`,
|
||||
`PUT /customer_assets/{id}` (name/serial/type/customer/properties — `name` required),
|
||||
`GET /customer_assets/{id}/patches`, `.../installed_applications`, `/rmm_alerts` (list +
|
||||
`{id}/mute` + DELETE clear), plus contacts, contracts, leads, vendors, purchase_orders,
|
||||
products, payments, wiki_pages, `/me`, `/search`. The `/syncro` skill now documents the
|
||||
asset-read/asset-write/rmm-alert/agent-deploy workflows; the full inventory is in
|
||||
`.claude/standards/syncro/api-reference.md`.
|
||||
- **Policy-folder move: VERIFIED WORKING with a full-scope token (2026-07-10).** Howard's
|
||||
token was upgraded to full scope; flip-and-restore on ACG-internal asset `12335235`
|
||||
(`692253 → 692278 → 692253`) succeeded, each confirmed by GET. `PUT /customer_assets/{id}`
|
||||
with `policy_folder_id` (+ required `name`) moves the asset. The spec: *"only
|
||||
policy_folder_id requires **Assets - Policy Change**; with other fields requires **Assets -
|
||||
Edit** AND **Assets - Policy Change**; nil/nonexistent/cross-customer folder → 422."*
|
||||
- **Route quirks:** `GET /policy_folders` (bare) → 404; `GET /policy_folders?customer_id=N` →
|
||||
200 (enumerate a customer's folders); `GET /policy_folders/{id}` → 401 even at full scope.
|
||||
- **Per-user scope differs — capability-check every scope-gated write.** A token WITHOUT
|
||||
Policy-Change returns HTTP 200 and silently no-ops the move (the exact 2026-06-25 symptom
|
||||
that produced the original "GUI-only" belief); `/me` shows coarse `asset:{write:true}` and
|
||||
cannot detect it. The `/syncro move-asset` helper preflights `GET /policy_folders?customer_id=N`
|
||||
(401 = no scope) and verifies the folder actually changed after the PUT. Only Howard's token
|
||||
is confirmed full-scope so far; grant the scope to each per-user token for consistent behavior.
|
||||
- **Archive (retire) IS genuinely GUI-only** — no archive endpoint, no `archived` field, and
|
||||
DELETE is destructive. That part of the old note stands.
|
||||
|
||||
**How to apply:** Do NOT attempt to build `/syncro move-asset` or any Syncro RMM policy/folder/group capability — it's not buildable on the public API. Don't re-probe these endpoints. The only API-drivable policy surface in the fleet is the `bitdefender` skill (GravityZone: create/assign policies, custom groups, move endpoints). For Syncro RMM policy work, direct the user to the Syncro web console. The `/syncro` skill stays PSA-only (tickets/billing/customers/scheduling/estimates + read-only asset lookup). See [[feedback-psa-default-syncro]].
|
||||
**How to apply:** Build/asset-manage freely via API for everything EXCEPT policy folders. To
|
||||
unlock `/syncro move-asset` + policy CRUD, add **Assets - Policy Change** + policy-folder
|
||||
read/manage to the Syncro API token (Admin → API Tokens), re-vault, then flip-and-restore
|
||||
verify on ACG-internal asset `12335235` (folder `692253`). Do NOT re-assert "can't do assets
|
||||
via API" — that was wrong. See [[feedback-psa-default-syncro]] and the api-reference doc.
|
||||
|
||||
435
.claude/standards/syncro/api-reference.md
Normal file
435
.claude/standards/syncro/api-reference.md
Normal file
@@ -0,0 +1,435 @@
|
||||
# Syncro API — Complete Endpoint Reference
|
||||
|
||||
Breadth reference for the **entire** Syncro REST API surface (~180 endpoints across 38
|
||||
resource types), generated from the live OpenAPI 3.0 spec
|
||||
(`https://api-docs.syncromsp.com/swagger.json`) and cross-checked against the production
|
||||
tenant on **2026-07-10**. Use this to reach ANY Syncro call — not just the PSA core.
|
||||
|
||||
- **The high-frequency, high-risk workflows** (tickets, billing/line-items, invoices,
|
||||
estimates, schedules, appointments, customers-read) are fully documented with verified
|
||||
response shapes, gotchas, and preview gates in **`.claude/commands/syncro.md`**. Do NOT
|
||||
duplicate that logic here — this file is the map; `syncro.md` is the operating manual for
|
||||
the core, and holds the detailed asset / RMM-alert / agent-deploy workflows too.
|
||||
- Everything below is reachable with the same auth + payload conventions as the core.
|
||||
|
||||
## Auth & conventions (same as syncro.md)
|
||||
|
||||
- **Base:** `https://computerguru.syncromsp.com/api/v1`
|
||||
- **Auth:** `?api_key=<key>` **query param** (NOT a header). Per-user keys resolved by
|
||||
`source .claude/scripts/syncro-env.sh` → `$SYNCRO_BASE` / `$SYNCRO_API_KEY`. Never hardcode.
|
||||
- **Every call is attributed to the key owner** — comments, line items, assets, invoices.
|
||||
- **Payload handoff:** heredoc `--data-binary @-` with `<<'JSON'` (static) or `<<JSON`
|
||||
(interpolating `${VARS}`). Never `/tmp/*.json` on Windows.
|
||||
- **Parsing:** Syncro emits unescaped control chars — `tr -d '\000-\037'` before jq, or use
|
||||
grep/sed. Never retry jq on a parse failure.
|
||||
- **Rate limit:** 180 req/min per IP. 429 → wait 60s.
|
||||
|
||||
## Write discipline (applies to EVERY POST/PUT/PATCH/DELETE here)
|
||||
|
||||
1. **Preview + explicit confirmation before any write** — show the full payload.
|
||||
2. **No retries on an ambiguous result** — GET the resource to confirm; Syncro has no
|
||||
idempotency, so a blind retry duplicates. Duplicates can't be deleted via API.
|
||||
3. **`[TEST]` prefix** on any test article; test against ACG-internal `customer_id 15353550`.
|
||||
4. **Bot alert after every successful write** (`post-bot-alert.sh`) — same as the core.
|
||||
5. **DELETE is immediate and unconfirmed** on most resources (schedules, estimates, assets
|
||||
caveat below) — never probe destructive methods against live customer data.
|
||||
|
||||
## Live token-capability matrix (howard key, probed 2026-07-10)
|
||||
|
||||
| Surface | Status with current per-user token |
|
||||
|---|---|
|
||||
| Tickets, Customers, Invoices, Estimates, Schedules, Appointments, Line Items | **OK** (core — see syncro.md) |
|
||||
| Assets: GET list/detail, **`/patches`**, **`/installed_applications`**, chat_info | **OK** (HTTP 200) |
|
||||
| Assets: POST create, PUT update (name/serial/type/customer/properties) | **OK** — `name` required |
|
||||
| Contacts, Contracts, Leads, Vendors, Purchase Orders, Payments, Products, Wiki, RMM Alerts | **OK** (HTTP 200) |
|
||||
| `/me`, `/users`, `/settings`, `/search` | **OK** |
|
||||
| **Policy Folders** (`GET/POST/PUT/DELETE /policy_folders`) | **401 — token lacks scope** |
|
||||
| **Asset move via `PUT /customer_assets/{id}` `policy_folder_id`** | **needs scope** (see below) |
|
||||
|
||||
### Policy-folder scope (corrects the old "RMM is GUI-only" belief)
|
||||
|
||||
The spec is explicit on `PUT /customer_assets/{id}`:
|
||||
> *"Updating only `policy_folder_id` requires **Assets - Policy Change**. Updating
|
||||
> `policy_folder_id` with other asset fields requires both **Assets - Edit** and **Assets -
|
||||
> Policy Change**. Nil, nonexistent, or cross-customer folder IDs return 422."*
|
||||
|
||||
Our per-user tokens currently **lack** the RMM/policy scope, so `/policy_folders` returns
|
||||
401 and a `policy_folder_id` PUT is silently ignored (HTTP 200, no change) — exactly the
|
||||
2026-06-25 symptom. This is a **token-scope gap, not an API limitation.** To enable
|
||||
policy-folder move + policy CRUD: in Syncro **Admin → API Tokens**, edit the token (or issue
|
||||
a Custom token) to add **Assets - Policy Change** and policy-folder read/manage permissions,
|
||||
re-vault it, then re-verify with a flip-and-restore on ACG-internal asset `12335235`
|
||||
(currently folder `692253`). Supersedes memory `reference-syncro-rmm-api-gui-only`.
|
||||
|
||||
## RMM-relevant reads (asset intelligence)
|
||||
|
||||
- `GET /customer_assets/{id}/patches` → `{available_patches, available_patches_meta,
|
||||
installed_patches}` — Windows patch posture per machine.
|
||||
- `GET /customer_assets/{id}/installed_applications` → `{installed_applications, meta}`
|
||||
(paginated) — software inventory; use to **verify an agent/app install** after a push.
|
||||
- `GET /rmm_alerts` → `{rmm_alerts, meta}` — live RMM alert feed; POST create, `{id}/mute`,
|
||||
DELETE clears.
|
||||
|
||||
---
|
||||
|
||||
# Calling any endpoint — worked templates
|
||||
|
||||
Every endpoint below uses the SAME auth + payload pattern (the Swagger UI "code examples" are
|
||||
generated from these same schemas). Copy a template, swap the path + body fields from the
|
||||
inventory. `${BASE}`/`${API_KEY}` come from `source .claude/scripts/syncro-env.sh`.
|
||||
|
||||
```bash
|
||||
# READ (GET) — list or single. Add filters as query params (e.g. customer_id, query, per_page).
|
||||
curl -s "${BASE}/<resource>?customer_id=${CUST_ID}&per_page=100&api_key=${API_KEY}" | tr -d '\000-\037' | jq '.'
|
||||
|
||||
# CREATE (POST) — body fields from the inventory's _body:_ line; _required:_ must be present.
|
||||
curl -s -X POST "${BASE}/<resource>?api_key=${API_KEY}" -H "Content-Type: application/json" \
|
||||
--data-binary @- <<'JSON'
|
||||
{ "field": "value" }
|
||||
JSON
|
||||
|
||||
# UPDATE (PUT/PATCH) — same shape; resend any field the endpoint marks required.
|
||||
curl -s -X PUT "${BASE}/<resource>/${ID}?api_key=${API_KEY}" -H "Content-Type: application/json" \
|
||||
--data-binary @- <<'JSON'
|
||||
{ "field": "new value" }
|
||||
JSON
|
||||
|
||||
# DELETE — immediate, no confirmation on most resources. Preview + confirm first.
|
||||
curl -s -X DELETE "${BASE}/<resource>/${ID}?api_key=${API_KEY}"
|
||||
```
|
||||
|
||||
Build bodies with `jq -nc --arg`/`--argjson` when interpolating shell values (JSON-safe). For
|
||||
any WRITE: preview the payload, confirm, then **GET the resource back to verify** (Syncro has
|
||||
no idempotency; a blind retry duplicates). Scope-gated writes can return 200 yet no-op — always
|
||||
verify the value actually changed (see the policy-folder note above).
|
||||
|
||||
---
|
||||
|
||||
# Full endpoint inventory (by resource)
|
||||
|
||||
Legend: methods are literal. `_required:_` lists must-send body fields; `_body:_` lists all
|
||||
accepted body fields (from the live schema). Absence of a body line = no JSON body / path-or-
|
||||
query only. `{...}` segments are path params.
|
||||
|
||||
## Appointment
|
||||
- `GET /appointments` — Returns a paginated list of Appointments
|
||||
- `POST /appointments` — Creates an Appointment
|
||||
_required:_ `summary,start_at`
|
||||
_body:_ all_day, appointment_duration, appointment_type_id, customer_id, description, do_not_email, email_customer, end_at, location, start_at, summary, ticket_id, user_id, user_ids
|
||||
- `DELETE /appointments/{id}` — Deletes an Appointment by ID
|
||||
- `GET /appointments/{id}` — Retrieves an Appointment by ID
|
||||
- `PUT /appointments/{id}` — Updates an existing Appointment by ID
|
||||
_required:_ `start_at`
|
||||
_body:_ all_day, appointment_duration, appointment_type_id, customer_id, description, email_customer, end_at, location, start_at, summary, ticket_id, user_id, user_ids
|
||||
|
||||
## Appointment Type
|
||||
- `GET /appointment_types` — Returns a paginated list of Appointment Types
|
||||
- `POST /appointment_types` — Creates an Appointment Type
|
||||
_required:_ `name`
|
||||
_body:_ email_instructions, location_hard_code, location_type, name
|
||||
- `DELETE /appointment_types/{id}` — Deletes an Appointment Type by ID
|
||||
- `GET /appointment_types/{id}` — Retrieves an Appointment Type by ID
|
||||
- `PUT /appointment_types/{id}` — Updates an existing Appointment Type by ID
|
||||
_body:_ email_instructions, location_hard_code, location_type, name
|
||||
|
||||
## Asset
|
||||
- `GET /customer_assets` — Returns a paginated list of Assets
|
||||
- `POST /customer_assets` — Creates an Asset
|
||||
_required:_ `name`
|
||||
_body:_ asset_serial, asset_type_id, asset_type_name, customer_id, name, properties
|
||||
- `GET /customer_assets/chat_information_by_ids` — Retrieves Assets chat informations by IDs
|
||||
- `GET /customer_assets/{id}` — Retrieves an Asset by ID
|
||||
- `PUT /customer_assets/{id}` — Updates an existing Asset by ID
|
||||
_required:_ `name`
|
||||
_body:_ asset_serial, asset_type_id, asset_type_name, customer_id, name, policy_folder_id, properties
|
||||
- `GET /customer_assets/{id}/installed_applications` — Retrieves installed applications for an Asset
|
||||
- `GET /customer_assets/{id}/patches` — Retrieves Windows patch data for an Asset
|
||||
|
||||
## Call
|
||||
- `GET /callerid` — Get Caller ID
|
||||
|
||||
## Canned Response
|
||||
- `GET /canned_responses` — Returns a list of Canned Responses with a query
|
||||
- `POST /canned_responses` — Creates a new Canned Response
|
||||
_required:_ `title,body`
|
||||
_body:_ body, canned_response_category_id, subject, title
|
||||
- `GET /canned_responses/settings` — Returns the settings for Canned Responses
|
||||
- `DELETE /canned_responses/{id}` — Deletes a Canned Response
|
||||
- `PATCH /canned_responses/{id}` — Updates a Canned Response
|
||||
_body:_ body, canned_response_category_id, subject, title
|
||||
|
||||
## Contact
|
||||
- `GET /contacts` — Returns a paginated list of Contacts
|
||||
- `POST /contacts` — Creates a Contact
|
||||
_required:_ `customer_id`
|
||||
_body:_ address1, address2, city, customer_id, email, enable_portal_user, extension, mobile, name, notes, phone, state, zip
|
||||
- `DELETE /contacts/{id}` — Deletes a Contact
|
||||
- `GET /contacts/{id}` — Retrieves a Contact by ID
|
||||
- `PUT /contacts/{id}` — Updates an existing Contact
|
||||
_required:_ `name`
|
||||
_body:_ address1, address2, city, customer_id, email, enable_portal_user, extension, mobile, name, notes, phone, state, title, zip
|
||||
|
||||
## Contract
|
||||
- `GET /contracts` — Returns a paginated list of Contracts
|
||||
- `POST /contracts` — Creates a Contract
|
||||
_required:_ `customer_id`
|
||||
_body:_ apply_to_all, contract_amount, customer_id, description, end_date, likelihood, name, primary_contact, sla_id, start_date, status
|
||||
- `DELETE /contracts/{id}` — Deletes a Contract by ID
|
||||
- `GET /contracts/{id}` — Retrieves a Contract by ID
|
||||
- `PUT /contracts/{id}` — Updates an existing Contract by ID
|
||||
_required:_ `customer_id`
|
||||
_body:_ apply_to_all, contract_amount, customer_id, description, end_date, likelihood, name, primary_contact, sla_id, start_date, status
|
||||
|
||||
## Customer
|
||||
- `GET /customers` — Returns a paginated list of customers
|
||||
- `POST /customers` — Creates a Customer
|
||||
- `GET /customers/autocomplete` — Returns a paginated list of customers for autocomplete query
|
||||
- `GET /customers/latest` — Returns latest Customer
|
||||
- `DELETE /customers/{id}` — Deletes a Customer by ID
|
||||
- `GET /customers/{id}` — Retrieves a Customer by ID
|
||||
- `PUT /customers/{id}` — Updates an existing Customer by ID
|
||||
|
||||
## Estimate
|
||||
- `GET /estimates` — Returns a paginated list of Estimates
|
||||
- `POST /estimates` — Creates an Estimate
|
||||
_body:_ created_at, customer_id, date, line_items, location_id, name, note, number, status, ticket_id, updated_at
|
||||
- `DELETE /estimates/{id}` — Deletes an Estimate by ID
|
||||
- `GET /estimates/{id}` — Retrieves an Estimate by ID or number
|
||||
- `PUT /estimates/{id}` — Updates an existing Estimate by ID
|
||||
_body:_ customer_id, date, location_id, name, note, number, status, ticket_id
|
||||
- `POST /estimates/{id}/convert_to_invoice` — Convert an Estimate to an Invoice
|
||||
- `POST /estimates/{id}/email` — Sends an Estimate to a Customer
|
||||
- `POST /estimates/{id}/line_items` — Adds a Line Item to an Estimate
|
||||
- `DELETE /estimates/{id}/line_items/{line_item_id}` — Deletes a Line Item
|
||||
- `PUT /estimates/{id}/line_items/{line_item_id}` — Updates a Line Item
|
||||
- `POST /estimates/{id}/print` — Queues a print job for an Estimate
|
||||
|
||||
## Invoice
|
||||
- `GET /invoices` — Returns a paginated list of Invoices
|
||||
- `POST /invoices` — Creates an Invoice
|
||||
_required:_ `customer_id,number,date`
|
||||
_body:_ balance_due, contact_id, created_at, customer_business_then_name, customer_id, date, due_date, hardwarecost, id, is_paid, line_items, location_id, note, number, pdf_url, po_number, subtotal, tax, tech_marked_paid, ticket_id, total, updated_at, verified_paid
|
||||
- `DELETE /invoices/{id}` — Deletes an invoice by ID
|
||||
- `GET /invoices/{id}` — Retrieves an Invoice by ID or Number
|
||||
- `PUT /invoices/{id}` — Updates an existing invoice by ID
|
||||
_body:_ contact_id, created_at, customer_business_then_name, customer_id, date, due_date, hardwarecost, location_id, note, number, pdf_url, po_number, subtotal, tax, ticket_id, total, updated_at
|
||||
- `POST /invoices/{id}/email` — Sends invoice to customer
|
||||
- `POST /invoices/{id}/print` — Queues a print job for an invoice
|
||||
- `GET /invoices/{id}/ticket` — Returns the associated ticket for an invoice
|
||||
|
||||
## Invoice/Line item
|
||||
- `POST /invoices/{id}/line_items` — Creates a new line item
|
||||
- `DELETE /invoices/{id}/line_items/{line_item_id}` — Deletes an a line item of an invoice by ID
|
||||
- `PUT /invoices/{id}/line_items/{line_item_id}` — Updates an a line item of an invoice by ID
|
||||
|
||||
## Item
|
||||
- `GET /items` — Returns a paginated list of Part Orders
|
||||
|
||||
## Lead
|
||||
- `GET /leads` — Returns a paginated list of Leads
|
||||
- `POST /leads` — Creates a Lead
|
||||
_body:_ address, appointment_time, appointment_type_id, business_name, city, contact_id, converted, customer_id, customer_purchase_id, disabled, email, first_name, from_check_in, hidden_notes, last_name, likelihood, location_id, mailbox_id, message_read, mobile, opportunity_amount_dollars, opportunity_start_date, phone, properties, signature_data, signature_date, signature_name, state, status, ticket_description, ticket_id, ticket_problem_type, ticket_properties, ticket_subject, ticket_type_id, user_id, zip
|
||||
- `GET /leads/{id}` — Retrieves a Lead by ID
|
||||
- `PUT /leads/{id}` — Updates an existing Lead by ID
|
||||
_body:_ address, appointment_time, appointment_type_id, business_name, city, contact_id, converted, customer_id, customer_purchase_id, disabled, email, first_name, from_check_in, hidden_notes, last_name, likelihood, location_id, mailbox_id, message_read, mobile, opportunity_amount_dollars, opportunity_start_date, phone, properties, signature_data, signature_date, signature_name, state, status, ticket_description, ticket_id, ticket_problem_type, ticket_properties, ticket_subject, ticket_type_id, user_id, zip
|
||||
|
||||
## Line Item
|
||||
- `GET /line_items` — Returns a paginated list of Line Items
|
||||
|
||||
## New Ticket Form
|
||||
- `GET /new_ticket_forms` — Returns a paginated list of Ticket Forms
|
||||
- `GET /new_ticket_forms/{id}` — Retrieves a Ticket Form
|
||||
- `POST /new_ticket_forms/{id}/process_form` — Creates a new Ticket for a Ticket Form
|
||||
_body:_ appointments, customer_details, ticket_details
|
||||
|
||||
## Payment
|
||||
- `GET /payments` — Returns a paginated list of Payments
|
||||
- `POST /payments` — Creates a Payment
|
||||
_body:_ address_city, address_street, address_zip, amount_cents, apply_payments, credit_card_number, customer_id, cvv, date_month, date_year, firstname, invoice_id, invoice_number, lastname, payment_method, ref_num, register_id, signature_data, signature_date, signature_name
|
||||
- `GET /payments/{id}` — Retrieves a Payment by ID
|
||||
|
||||
## Payment Method
|
||||
- `GET /payment_methods` — Returns a paginated list of Payment Methods
|
||||
|
||||
## Payment Profile
|
||||
- `GET /customers/{customer_id}/payment_profiles` — Returns a paginated list of Payment Profiles
|
||||
- `POST /customers/{customer_id}/payment_profiles` — Creates a Payment Profile
|
||||
_body:_ customer_external_id, expiration, last_four, payment_profile_id
|
||||
- `DELETE /customers/{customer_id}/payment_profiles/{id}` — Deletes a Payment Profile
|
||||
- `GET /customers/{customer_id}/payment_profiles/{id}` — Retrieves a Payment Profile by ID
|
||||
- `PUT /customers/{customer_id}/payment_profiles/{id}` — Updates a Payment Profile
|
||||
_body:_ expiration, last_four
|
||||
|
||||
## Phone
|
||||
- `GET /customers/{customer_id}/phones` — Returns a paginated list of Phones
|
||||
- `POST /customers/{customer_id}/phones` — Creates a Phone
|
||||
- `DELETE /customers/{customer_id}/phones/{id}` — Deletes a Phone by ID
|
||||
- `PUT /customers/{customer_id}/phones/{id}` — Updates an existing Phone by ID
|
||||
|
||||
## Policy Folder
|
||||
- `GET /policy_folders` — Returns a paginated list of Policy Folders
|
||||
- `POST /policy_folders` — Creates a Policy Folder
|
||||
_required:_ `customer_id,name,parent_id`
|
||||
_body:_ customer_id, name, parent_id
|
||||
- `DELETE /policy_folders/{id}` — Deletes a Policy Folder by ID
|
||||
- `GET /policy_folders/{id}` — Retrieves a Policy Folder by ID
|
||||
- `PUT /policy_folders/{id}` — Updates an existing Policy Folder by ID
|
||||
_body:_ name, parent_id, partial_policy_id
|
||||
|
||||
## Portal User
|
||||
- `GET /portal_users` — Returns a paginated list of Portal Users
|
||||
- `POST /portal_users` — Creates a Portal User
|
||||
- `POST /portal_users/create_invitation` — Creates an Invitation for a Portal User
|
||||
_body:_ id
|
||||
- `DELETE /portal_users/{id}` — Deletes a Portal User by ID
|
||||
- `PUT /portal_users/{id}` — Updates an existing Portal User by ID
|
||||
|
||||
## Product
|
||||
- `GET /products` — Returns a paginated list of Products
|
||||
- `POST /products` — Creates a Product
|
||||
_required:_ `name,description`
|
||||
_body:_ category_ids, condition, description, desired_stock_level, disabled, discount_percent, maintain_stock, name, notes, physical_location, price_cost, price_retail, price_wholesale, product_category, product_skus_attributes, qb_item_id, quantity, reorder_at, serialized, sort_order, tax_rate_id, taxable, upc_code, vendor_ids, warranty, warranty_template_id
|
||||
- `GET /products/barcode` — Returns a Product by Barcode
|
||||
- `GET /products/categories` — Returns a paginated list of Product Categories
|
||||
- `GET /products/{id}` — Retrieves a Product by ID
|
||||
- `PUT /products/{id}` — Updates an existing Product by ID
|
||||
_required:_ `name,description`
|
||||
_body:_ category_ids, condition, description, desired_stock_level, disabled, discount_percent, maintain_stock, name, notes, physical_location, price_cost, price_retail, price_wholesale, product_category, product_skus_attributes, qb_item_id, quantity, reorder_at, serialized, sort_order, tax_rate_id, taxable, upc_code, vendor_ids, warranty, warranty_template_id
|
||||
- `POST /products/{id}/add_images` — Creates a Product Image
|
||||
- `DELETE /products/{id}/delete_image` — Deletes a Product Image
|
||||
- `PUT /products/{id}/location_quantities` — Updates a Location Quantity
|
||||
_body:_ location_quantity_id, quantity
|
||||
|
||||
## Product Serial
|
||||
- `GET /products/{product_id}/product_serials` — Returns a paginated list of Product_serials
|
||||
- `POST /products/{product_id}/product_serials` — Creates a Product Serial
|
||||
_body:_ condition, price_cost_cents, price_retail_cents, serial_number
|
||||
- `POST /products/{product_id}/product_serials/attach_to_line_item` — Adds Product Serials to a Line Item
|
||||
_body:_ line_item_id, product_serial_ids, record_type
|
||||
- `PUT /products/{product_id}/product_serials/{id}` — Updates an existing Product Serial by ID
|
||||
_body:_ condition, notes, price_cost_cents, price_retail_cents, serial_number
|
||||
|
||||
## Product Sku
|
||||
- `GET /products/{product_id}/product_skus` — Returns list of Product Skus
|
||||
- `POST /products/{product_id}/product_skus` — Creates a Product Sku
|
||||
_body:_ value, vendor_id
|
||||
- `PUT /products/{product_id}/product_skus/{id}` — Updates an existing Product Sku by ID
|
||||
_body:_ value, vendor_id
|
||||
|
||||
## Purchase Order
|
||||
- `GET /purchase_orders` — Returns a paginated list of Purchase Orders
|
||||
- `POST /purchase_orders` — Creates a Purchase Order
|
||||
_body:_ delivery_tracking, discount_percent, due_date, expected_date, general_notes, location_id, order_date, other_cents, paid_date, shipping_cents, shipping_notes, user_id, vendor_id
|
||||
- `GET /purchase_orders/{id}` — Retrieves a Purchase Order by ID
|
||||
- `POST /purchase_orders/{id}/create_po_line_item` — Adds a Product to a Purchase Order
|
||||
_body:_ product_id, quantity
|
||||
- `POST /purchase_orders/{id}/receive` — receive purchase_order
|
||||
_body:_ line_item_id
|
||||
|
||||
## RMM Alert
|
||||
- `GET /rmm_alerts` — Returns a paginated list of RMM Alerts
|
||||
- `POST /rmm_alerts` — Creates an RMM Alert
|
||||
_body:_ rmm_alert
|
||||
- `DELETE /rmm_alerts/{id}` — Deletes/Clears an RMM Alert by ID
|
||||
- `GET /rmm_alerts/{id}` — Retrieves an RMM Alert by ID
|
||||
- `POST /rmm_alerts/{id}/mute` — Mutes an RMM Alert by ID
|
||||
|
||||
## Schedule
|
||||
- `GET /schedules` — Returns a paginated list of Invoice Schedules
|
||||
- `POST /schedules` — Creates an Invoice Schedule
|
||||
- `DELETE /schedules/{id}` — Deletes a Schedule by ID
|
||||
- `GET /schedules/{id}` — Retrieves a Schedule by ID
|
||||
- `PUT /schedules/{id}` — Updates an existing Invoice Schedule by ID
|
||||
- `POST /schedules/{id}/add_line_item` — Adds a Line Item to an Invoice Schedule
|
||||
- `PUT /schedules/{id}/line_items/{schedule_line_item_id}` — Updates a Line Item
|
||||
- `POST /schedules/{id}/remove_line_item` — Removes a Line Item from an Invoice Schedule
|
||||
|
||||
## Search
|
||||
- `GET /search` — Search all the things
|
||||
|
||||
## Setting
|
||||
- `GET /settings` — Returns a list of Account Settings
|
||||
- `GET /settings/printing` — Returns Printing Settings
|
||||
- `GET /settings/tabs` — Returns Tabs Settings
|
||||
|
||||
## Ticket
|
||||
- `GET /ticket_comments` — Returns a paginated flat list of comments across multiple tickets
|
||||
- `GET /tickets` — Returns a paginated list of Tickets
|
||||
- `POST /tickets` — Creates a Ticket
|
||||
- `GET /tickets/settings` — Returns Tickets Settings
|
||||
- `DELETE /tickets/{id}` — Deletes a Ticket by ID
|
||||
- `GET /tickets/{id}` — Retrieves a Ticket by ID
|
||||
- `PUT /tickets/{id}` — Updates an existing Ticket by ID
|
||||
- `POST /tickets/{id}/add_line_item` — Creates a Ticket Line Item
|
||||
_body:_ description, name, price_cost, price_retail, product_id, quantity, taxable, upc_code
|
||||
- `POST /tickets/{id}/attach_file_url` — Attach a file to a Ticket
|
||||
- `POST /tickets/{id}/charge_timer_entry` — Charges a Ticket Timer
|
||||
- `POST /tickets/{id}/comment` — Adds a Comment to a Ticket
|
||||
_body:_ body, do_not_email, hidden, sms_body, subject, tech
|
||||
- `GET /tickets/{id}/comments` — Returns Comments for a Ticket
|
||||
- `POST /tickets/{id}/delete_attachment` — Deletes a Ticket Attachment
|
||||
_body:_ attachment_id
|
||||
- `POST /tickets/{id}/delete_timer_entry` — Deletes a Ticket Timer
|
||||
- `POST /tickets/{id}/print` — Prints a Ticket by ID
|
||||
- `POST /tickets/{id}/remove_line_item` — Deletes a Ticket Line Item
|
||||
_body:_ ticket_line_item_id
|
||||
- `POST /tickets/{id}/timer_entry` — Create a Ticket Timer for a Ticket
|
||||
_body:_ duration_minutes, end_at, notes, product_id, start_at, user_id
|
||||
- `PUT /tickets/{id}/update_line_item` — Updates an existing Ticket Line Item
|
||||
_body:_ description, name, price_cost, price_retail, product_id, quantity, taxable, ticket_line_item_id, upc_code
|
||||
- `PUT /tickets/{id}/update_timer_entry` — Updates an existing Ticket Timer
|
||||
_body:_ duration_minutes, notes, product_id, start_at, timer_entry_id, user_id
|
||||
|
||||
## Ticket Blueprint
|
||||
- `GET /ticket_blueprints` — Returns a paginated list of Ticket Blueprints
|
||||
- `POST /ticket_blueprints/{id}/apply` — Creates tickets from a Blueprint
|
||||
_required:_ `customer_id`
|
||||
_body:_ customer_id
|
||||
|
||||
## Ticket Timer
|
||||
- `GET /ticket_timers` — Returns a paginated list of Ticket Timers
|
||||
- `PATCH ticket_timers/{id}` — Update the billable property of a Ticket Timer
|
||||
|
||||
## Timelog
|
||||
- `GET /timelogs` — Returns a paginated list of Timelogs
|
||||
- `PUT /timelogs` — Updates a Timelog
|
||||
_body:_ in_at, in_note, lunch, out_at, out_note
|
||||
- `GET /timelogs/last` — Returns last Timelog
|
||||
|
||||
## User
|
||||
- `GET /me` — Returns the current user
|
||||
- `POST /otp_login` — Authorize a User with One Time Password
|
||||
_body:_ code
|
||||
- `GET /users` — Returns a paginated list of Users
|
||||
- `GET /users/{id}` — Retrieves an existing User by ID
|
||||
|
||||
## User Device
|
||||
- `POST /user_devices` — Creates a User Device
|
||||
_body:_ device_name, device_uuid, model, registration_token_gcm, screen_size, system_name
|
||||
- `GET /user_devices/{id}` — Retrieves an existing User Device by UUID
|
||||
- `PUT /user_devices/{id}` — Updates an existing User Device by UUID
|
||||
_body:_ registration_token_gcm
|
||||
|
||||
## Vendor
|
||||
- `GET /vendors` — Returns a paginated list of Vendors
|
||||
- `POST /vendors` — Creates a Vendor
|
||||
- `GET /vendors/{id}` — Retrieves a Vendor Page
|
||||
- `PUT /vendors/{id}` — Updates an existing Vendor page by ID
|
||||
|
||||
## Wiki Page
|
||||
- `GET /wiki_pages` — Returns a paginated list of Wiki Pages
|
||||
- `POST /wiki_pages` — Creates a Wiki Page
|
||||
_body:_ asset_id, body, customer_id, name, slug, visibility
|
||||
- `DELETE /wiki_pages/{id}` — Deletes a Wiki Page by ID
|
||||
- `GET /wiki_pages/{id}` — Retrieves a Wiki Page
|
||||
- `PUT /wiki_pages/{id}` — Updates an existing Wiki Page by ID
|
||||
_body:_ asset_id, body, customer_id, name, slug, visibility
|
||||
|
||||
## Worksheet Result
|
||||
- `GET /tickets/{ticket_id}/worksheet_results` — Returns a paginated list of Worksheet Results
|
||||
- `POST /tickets/{ticket_id}/worksheet_results` — Creates Worksheet Result
|
||||
_body:_ title, worksheet_template_id
|
||||
- `DELETE /tickets/{ticket_id}/worksheet_results/{id}` — Deletes a Worksheet Result
|
||||
- `GET /tickets/{ticket_id}/worksheet_results/{id}` — Retrieves a Worksheet Result by ID
|
||||
- `PUT /tickets/{ticket_id}/worksheet_results/{id}` — Updates a Worksheet Result
|
||||
_body:_ answers, complete, public, required, title, user_id, worksheet_template_id
|
||||
@@ -19,6 +19,8 @@ Categories (the `[type]` tag): _(none)_ = skill/command execution failure ·
|
||||
|
||||
<!-- Append entries below this line -->
|
||||
|
||||
2026-07-10 | Howard-Home | syncro/assets | [correction] assumed asset writes (POST/PUT create-update), policy_folder_id move, patches, and installed_applications were not doable via the public API; correct is the endpoints exist per the live OpenAPI spec and the prior 'policy_folder_id silently ignored' was a token missing the 'Assets - Policy Change' scope, not an API limitation [ctx: ref=reference-syncro-rmm-api-gui-only endpoint=PUT_/customer_assets/{id}]
|
||||
|
||||
2026-07-10 | GURU-BEAST-ROG | guruscan | detached full-chain wrapper died mid-RKill on TPM-PC (marker never written, task->Ready, rkill.log completed fine as orphan) — RKill's process sweep almost certainly killed its own hidden SYSTEM powershell parent; relaunched with -SkipScanners RKill [ctx: host=TPM-PC client=mineralogical-record chain-design-issue]
|
||||
|
||||
2026-07-10 | GURU-5070 | packetdial | HTTP 404 GET https://pbx.packetdial.com/ns-api/v2/domains/vwp.91912.service/users/100: {"code":404,"message":"Resource not found."} [ctx: cmd=user] (x2)
|
||||
|
||||
Reference in New Issue
Block a user