# Session — yealink-ymcs skill built (YMCS v2 API), pairs with packetdial; vendor-stack correction

## User
- **User:** Mike Swanson (mike)
- **Machine:** GURU-5070
- **Role:** admin

## Session Summary

Continuation of the VoIP onboarding work. Kept the `vwp.91912.service` domain created last
session and fixed its `domain-type` to `Standard` (the create had stored `"no"`). Then built the
`onboard-domain` wrapper into the packetdial skill — one gated command running the GUI Add-a-Domain
flow as 3 API calls (POST /domains → addresses/validate → addresses/create), verified end-to-end
with a throwaway create→delete. Mike then clarified the vendor stack (PacketDial = ACG's VoIP-dept
brand; NetSapiens = the platform; OIT/OITVOIP = white-label wholesaler) — captured as a reference
memory + logged correction.

Mike's main ask: add the related service — **Yealink cloud device manager** — and make sure we have
access + can use it with the PBX skill. Discovery found ACG already has a Yealink admin
(`admin@azcomputerguru.com`, vault `infrastructure/voip-phones.sops.yaml`) and that the cloud
manager is **YMCS** (`us.ymcs.yealink.com`); the wiki documents VWP's Yealink T54W fleet. Mike then
supplied **YMCS API credentials** (AccessKey ID + Secret) via screenshot, which were vaulted at
`services/yealink-ymcs.sops.yaml`.

On Mike's go, RTFM'd the YMCS open API and built a new **`yealink-ymcs` skill**. The current v2 API
uses token auth (`POST /v2/token` Basic → bearer), not the legacy RPS HMAC signing; Yealink servers
require legacy TLS renegotiation. Harvested the full `/v2/dm/*` + `/v2/rps/*` endpoint map from the
`dszp/n8n-nodes-yealinkymcs` community node. Implemented `ymcs_client.py` (token+bearer, legacy-TLS
SSL context, paginated `_list`, vault creds) + `ymcs.py` CLI (reads + gated writes + raw). Live-
verified: token auth, sites, devices (21), accounts, rps-servers. **The ACG AccessKey is the parent
account that sees ALL client sites** (VWP, GuruHQ, Ace Pick Up Parks as children) — so one key
covers every client fleet; answers Mike's per-client-vs-one-key question.

The pbx+phone pipeline now exists end to end: `packetdial onboard-domain` (PBX domain + SIP
accounts) → `yealink-ymcs` (`add-devices-by-mac`, `add-sipaccount` to push a NetSapiens SIP cred
onto a phone, `rps-add` for zero-touch via RPS server `WL - ACG` = `ftp://p.packetdials.net`). VWP
is the live pilot (onboarded to the PBX; 21 devices in YMCS, fleet partly pending). Everything
committed, pushed, deployed.

## Key Decisions
- **Token auth, not legacy HMAC.** The first sources described the old RPS Management Platform HMAC
  signing (X-Ca-* + Content-MD5); the n8n node revealed the current v2 API uses `POST /v2/token`
  (Basic) → bearer. Built to v2 (simpler, current).
- **Legacy TLS handled in-client** via an SSL context with `OP_LEGACY_SERVER_CONNECT` (Yealink
  servers require renegotiation; default Python TLS would fail).
- **One AccessKey for all clients** — verified the key is the ACG parent account; client sites are
  children. No per-client API keys.
- **`add-sipaccount` instead of a sip-account list.** `/v2/dm/sipAccounts` (POST) is the CREATE
  endpoint (not a list); wrapped it as the gated write since pushing a SIP cred onto a phone is the
  actual PBX↔phone integration glue. No list-sip-accounts exists in v2.
- **Read-first, writes gated --confirm** (mirrors packetdial); `raw` passthrough for the rest.
- **Honest [V]/[P] status in docs** — only token/sites/devices/accounts/rps-servers lifecycle-
  verified; writes + official-firmwares + `--site` filter are plumbed-but-unverified (no throwaway
  device to safely test writes on).

## Problems Encountered
- **MSYS path conversion on `raw`:** a leading `/v2/...` arg gets rewritten by Git-Bash to
  `C:/Program Files/Git/v2/...`. Added in-code recovery (cut back to `/v2/`). `MSYS_NO_PATHCONV=1`
  is NOT a fix — it broke the vault subprocess (creds failed). Named wrappers (hardcoded paths)
  are unaffected. (Documented Windows gotcha class.)
- **`/v2/dm/sipAccounts` is a create, not a list** — my initial `sipaccounts` read wrapper hit it
  and got HTTP 400 (`sipServer1 cannot be null`). Replaced with the gated `add-sipaccount`.
- **`official-firmwares`** returns a non-standard shape (likely needs a model param) — marked [P].
- **`--site` filter on `devices`** returned all 21 (siteId in the body didn't scope) — best-effort,
  marked [P]; full data still returned.
- **cp1252 + WebFetch 403** while RTFMing — used `encoding='utf-8'` for spec reads and pivoted from
  the 403'd Yealink doc to the public GitHub n8n node source via the GitHub API + raw files.
- **`domain-type` stored `"no"` on packetdial create** — fixed vwp via `raw PUT` (carryover).

## Configuration Changes
Created:
- `.claude/skills/yealink-ymcs/SKILL.md`, `scripts/ymcs.py`, `scripts/ymcs_client.py` (new skill).
- `services/yealink-ymcs.sops.yaml` (VAULT) — YMCS API AccessKey.
- `.claude/memory/reference_packetdial_oit_netsapiens.md` (+ MEMORY.md index line).
- This session log.
Modified:
- `.claude/skills/packetdial/SKILL.md`, `scripts/ns.py`, `scripts/ns_client.py` — `onboard-domain`
  wrapper.
- `errorlog.md` — vendor-model correction.
Live changes: vwp `domain-type` → Standard; threw away `acg-onbtest.91912.service` (wrapper test).

## Credentials & Secrets
- **YMCS API AccessKey** — vaulted `services/yealink-ymcs.sops.yaml`:
  `credentials.access_key_id` / `credentials.access_key_secret`. Region us
  (`us-api.ymcs.yealink.com`). Read with `vault.sh get-field services/yealink-ymcs.sops.yaml
  credentials.access_key_id`. (Values intentionally not echoed here — they're vaulted.)
- Existing: Yealink portal admin `admin@azcomputerguru.com` → `infrastructure/voip-phones.sops.yaml`;
  per-client portal logins e.g. `clients/valleywide/`. PBX reseller key `msp-tools/oitvoip.sops.yaml`.

## Infrastructure & Servers
- YMCS API: `https://us-api.ymcs.yealink.com` (v2). Auth `POST /v2/token` Basic→bearer; legacy TLS.
- YMCS account tree (ACG parent key): `Arizona Computer Guru LLC` (id 34a602e7…) → `VWP`
  (1e7578a6…), `GuruHQ` (04117c54…), `Ace Pick Up Parks` (0320c810…).
- RPS server: `WL - ACG` (id 019c908f…), url `ftp://p.packetdials.net`, authName `lrshwh`.
- Device object keys: id, mac, sn, name, modelId/modelName, siteId/siteName, programVersion,
  deviceStatus, connectWay, wanIp, lanIp, lastReportTime, createTime. 21 devices total.
- PBX: `pbx.packetdial.com/ns-api/v2` (NetSapiens via OIT); domain `vwp.91912.service` live.

## Commands & Outputs
- YMCS reads: `ymcs.py sites|devices|accounts|rps-servers|firmwares|models|alarms|oplogs|device-groups|device-configs`
- YMCS gated writes: `ymcs.py add-devices-by-mac|add-sipaccount|reboot|reset|rps-add|rps-del --body '{...}' --confirm`
- `ymcs.py raw POST /v2/dm/listDevices --body '{"skip":0,"limit":50,"autoCount":true}'` (path auto-recovers MSYS mangling)
- packetdial onboarding: `ns.py onboard-domain --body-file new-client.json --confirm`
- vwp fix: `ns.py raw PUT domains/vwp.91912.service --body '{"domain-type":"Standard"}' --confirm`

## Pending / Incomplete Tasks
- **[P] YMCS writes unverified** — add-devices-by-mac, add-sipaccount, reboot, reset, rps add/del;
  official-firmwares shape; `--site` device filter. Exercise on real devices (VWP's pending fleet)
  when a task needs them.
- **Proposed (not started):** a combined `onboard-client` flow = packetdial onboard-domain +
  users/devices + YMCS phone provisioning (add-by-mac + add-sipaccount/rps) in one gated run,
  piloted on VWP's 11 pending phones.
- **packetdial gaps (carryover):** voicemail user-defaults (not in POST /domains), `email-send-from-address`
  pending `voicemail@`/`noreply@packetdial.com` mailbox creation.
- `OIT-API.txt` plaintext in Downloads — Mike to delete.

## Reference Information
- New skill: `.claude/skills/yealink-ymcs/`. Vendor stack memory: `reference_packetdial_oit_netsapiens`.
- Endpoint map source: `github.com/dszp/n8n-nodes-yealinkymcs` (n8n community node) + Yealink "Open
  API for YMCS V4X" doc. Auth doc patterns: support.yealink.com (legacy RPS HMAC — superseded by v2 token).
- Commits this session: `onboard-domain` `d1d1302d`; vendor-stack memory `1dbefd54`; yealink-ymcs
  skill `850e685d`. Vault: oitvoip key + YMCS key pushed.
- YMCS endpoints: `/v2/token`, `/v2/dm/{listSites,listDevices,listAccounts,sipAccounts,addDevicesByMac,
  addDevices,delDevices,device/reboot,device/reset,listFirmwares,models,...}`, `/v2/rps/{listServers,
  listDevices,addDevices,deleteDevices}`.
