diff --git a/.claude/commands/syncro.md b/.claude/commands/syncro.md index 5943b69..4656c0a 100644 --- a/.claude/commands/syncro.md +++ b/.claude/commands/syncro.md @@ -19,7 +19,7 @@ Create, update, close, comment on, and bill tickets in Syncro PSA. ## API Configuration **Base URL:** `https://computerguru.syncromsp.com/api/v1` -**API Key:** SOPS vault `msp-tools/syncro.sops.yaml` → `credentials.credential` +**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/ @@ -27,21 +27,65 @@ Create, update, close, comment on, and bill tickets in Syncro PSA. When invoked, use the Syncro REST API via `curl`. All requests include `?api_key=` as query parameter (NOT in header — Syncro uses query param auth). +### Attribution rule (CRITICAL) + +Every Syncro API call is attributed to the **owner of the API key**. Comments, line items, timer entries, and invoices created by the API are logged as the API user — regardless of who is running the command. So the skill MUST use a per-user API key that matches the actual tech running it, or comments will be misattributed. + +| Vault entry | Syncro user | user_id | +|---|---|---| +| `msp-tools/syncro-howard.sops.yaml` | Howard Enos | 1750 | +| `msp-tools/syncro.sops.yaml` | Michael Swanson | 1735 (current shared fallback) | + +When Mike generates his own per-user key, add `msp-tools/syncro-mike.sops.yaml` and demote the shared entry or remove it entirely. + ### Get API key ```bash -# Vault path comes from .claude/identity.json (per-machine) via the ClaudeTools wrapper VAULT="$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh" -API_KEY=$(bash "$VAULT" get-field msp-tools/syncro.sops.yaml credentials.credential) BASE="https://computerguru.syncromsp.com/api/v1" + +# Select key by identity.json user; fall back to shared key if per-user missing +USER_ID=$(jq -r '.user // empty' "$CLAUDETOOLS_ROOT/.claude/identity.json") +KEY_PATH="msp-tools/syncro-${USER_ID}.sops.yaml" +if ! bash "$VAULT" list 2>/dev/null | grep -qx "${KEY_PATH}"; then + echo "[WARN] No per-user Syncro key at ${KEY_PATH} — falling back to shared key. Actions will be attributed to the shared key owner." >&2 + KEY_PATH="msp-tools/syncro.sops.yaml" +fi +API_KEY=$(bash "$VAULT" get-field "$KEY_PATH" credentials.credential) ``` -If `vault.sh get-field` fails (yq not installed), fall back to: +Verify attribution before destructive operations: ```bash -VAULT_ROOT=$(bash "$VAULT" get msp-tools/syncro.sops.yaml 2>/dev/null | head -1 || python3 -c "import json; print(json.load(open('$CLAUDETOOLS_ROOT/.claude/identity.json'))['vault_path'])") -API_KEY=$(sops -d "$VAULT_ROOT/msp-tools/syncro.sops.yaml" | py -c "import sys,yaml; print(yaml.safe_load(sys.stdin)['credentials']['credential'])") +ME=$(curl -s "${BASE}/me?api_key=${API_KEY}" | jq -r '.user_name + " (user_id=" + (.user_id|tostring) + ")"') +echo "Authenticated as: $ME" ``` +### Adding a per-user key + +1. User logs into Syncro → Admin → API Tokens → New (`/api_tokens/new`) +2. Type: Integration API Token (or Custom with all standard scopes: asset/customer/ticket/invoice/payment read+write+delete, worksheet add+manage+delete, chat + script.execute) +3. Copy the token once (Syncro only shows it on creation) +4. Encrypt to vault: + ```bash + cat > $VAULT_ROOT/msp-tools/syncro-.sops.yaml <) + subdomain: computerguru + api-base-url: https://computerguru.syncromsp.com/api/v1 + api-docs: https://api-docs.syncromsp.com/ + status: active + owner: + syncro_user_id: + tags: [msp-tools, per-user] + credentials: + credential: + notes: Per-user Syncro API token for . Created YYYY-MM-DD. + YAML + # MUST run from vault root so sops picks up .sops.yaml + (cd "$VAULT_ROOT" && sops --encrypt --in-place "msp-tools/syncro-.sops.yaml") + ``` +5. Commit + push vault repo. + ### Endpoints reference #### Tickets diff --git a/.claude/messages/for-mike.md b/.claude/messages/for-mike.md index 8a7ea19..15456a2 100644 --- a/.claude/messages/for-mike.md +++ b/.claude/messages/for-mike.md @@ -4,6 +4,26 @@ Check this file at sync. Delete items after you've addressed them. --- +## From Howard, 2026-04-22 — Per-user Syncro keys (attribution fix) + +I hit the issue that my Syncro comments/line items on ticket #32179 were getting logged as you (user_id 1735) because we share your API key. Fixed it with per-user tokens: + +- Generated my own Syncro API token (Custom, admin, indefinite) → `user_id 1750` +- Added vault entry: `msp-tools/syncro-howard.sops.yaml` +- Patched `.claude/commands/syncro.md` to pick the key from `identity.json`'s `user` field, falls back to the shared `msp-tools/syncro.sops.yaml` if no per-user file exists +- Verified `/me` now returns Howard Enos on my machine + +**When you get a chance** (after Valleywide settles), do the same for yourself so the shared key can be retired: + +1. Syncro → Admin → API Tokens → New (integration or custom, full scopes) +2. `cat > $VAULT_ROOT/msp-tools/syncro-mike.sops.yaml <