Files
claudetools/wiki/systems/packetdial.md
Mike Swanson a21d24f09f wiki: compile packetdial (seed) — ACG VoIP system (PacketDial/NetSapiens/OIT + Yealink YMCS)
New systems article covering the full VoIP stack: vendor model (PacketDial brand / NetSapiens
platform / OIT wholesaler / YMCS phones), the PBX API + packetdial skill (reads + gated writes +
onboard-domain), the Yealink/YMCS side + yealink-ymcs skill (one ACG key -> all client sites, RPS),
the onboarding pipeline, vault paths, and known gotchas. Sources: the 3 06-22 session logs + both
skill docs + the vendor-stack memory. Indexed under Systems.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 10:13:00 -07:00

309 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: system
name: packetdial
display_name: "PacketDial (ACG VoIP / NetSapiens via OIT)"
last_compiled: 2026-06-22
compiled_by: GURU-5070/claude-main
sources:
- .claude/memory/reference_packetdial_oit_netsapiens.md
- .claude/skills/packetdial/SKILL.md
- .claude/skills/packetdial/references/api.md
- .claude/skills/yealink-ymcs/SKILL.md
- session-logs/2026-06/2026-06-22-mike-packetdial-buildout-oitvoip-vault.md
- session-logs/2026-06/2026-06-22-mike-packetdial-domain-onboarding-vwp.md
- session-logs/2026-06/2026-06-22-mike-yealink-ymcs-skill.md
- wiki/clients/valleywide.md
backlinks:
- clients/valleywide
---
# PacketDial (ACG VoIP / NetSapiens via OIT)
> Two things live under this article: (1) the **PacketDial/NetSapiens PBX platform** — ACG's
> hosted VoIP offering, managed via the `packetdial` skill against the NetSapiens API v2; and
> (2) the **Yealink YMCS phone-management layer** — the cloud device manager that handles
> physical phone provisioning, firmware, and RPS zero-touch, managed via the `yealink-ymcs`
> skill. The two skills form the end-to-end onboarding pipeline for new VoIP clients.
---
## Vendor Stack
Do not conflate these four layers:
| Layer | What it is | Hostname / Identifier |
|---|---|---|
| **PacketDial** | ACG's own VoIP department / customer-facing brand | `pbx.packetdial.com` (API host); `voip.packetdial.com` (customer portal — no API) |
| **NetSapiens** | The underlying PBX/UCaaS software platform (SNAPsolution API v2, v44.4.10) | Same hosts as above |
| **OIT / OITVOIP** | White-label wholesale provider that runs NetSapiens and resells to MSPs; ACG's upstream | `api.ucaasnetwork.com` (OIT/NetSapiens white-label host — same platform as `pbx.packetdial.com`) |
| **YMCS (Yealink Management Cloud Service)** | Separate Yealink cloud for physical phone device management | `us-api.ymcs.yealink.com` (US region) |
`pbx.packetdial.com` and `api.ucaasnetwork.com` resolve to the same NetSapiens platform under
different white-label hostnames. The `packetdial` skill targets `pbx.` by default; if a future
endpoint 403s there, override with `PACKETDIAL_API_BASE_URL=https://api.ucaasnetwork.com/ns-api/v2`.
ACG's reseller territory on OIT's platform is `91912.service`.
---
## PBX Platform (NetSapiens via PacketDial / OIT)
### API
| Field | Value |
|---|---|
| Base URL | `https://pbx.packetdial.com/ns-api/v2` |
| Token endpoint | `https://pbx.packetdial.com/ns-api/v2/tokens` |
| Spec version | NetSapiens API v2 v44.4.10 — 239 paths / 354 operations (GET 139, POST 87, PUT 68, DELETE 50, PATCH 10) |
| OpenAPI spec | `https://pbx.packetdial.com/ns-api/webroot/openapi/openapi.json` |
| Swagger UI | `https://pbx.packetdial.com/ns-api/openapi` |
| Scope | Almost everything nested under `/domains/{domain}/...` |
**Auth — static API key (in use):** Send the `nsr_` key directly as `Authorization: Bearer <key>`
no exchange step. Key prefix encodes scope: `nsr_` = reseller, `nss_` = system, `nsd_` = domain.
OAuth2 password grant (`POST /tokens` form-encoded) is also supported; the client caches and
refreshes JWTs ~60s before expiry.
**Reseller key (live-verified 2026-06-22):**
| Field | Value |
|---|---|
| Key-ID | `nsr_hSGUB5Wo` |
| Scope | Reseller `91912.service`; `user/domain: *` (sees every domain under ACG's territory) |
| Permissions | Read-write (`readonly: no`); cannot create sub-keys (`can-create-keys: no`) |
| Vault path | `msp-tools/oitvoip.sops.yaml``credentials.api_key` |
### Reseller Domains (as of 2026-06-22)
| Domain | Purpose | Notes |
|---|---|---|
| `0000.91912.service` | Reseller default / template | OIT platform default |
| `arizonacomputerguru` | ACG's own domain | **Sanctioned test bed — not in production use.** Use for exercising write wrappers safely. |
| `russo.91912.service` | Client domain | |
| `vwp.91912.service` | Valley Wide Plastering | Created 2026-06-22 via `onboard-domain`. E911 address id `a-6a395c03d4cfe` (301 N 56TH ST, Chandler AZ 85226, USPS VALID). Caller-ID 4807059500. |
### `packetdial` Skill
Skill location: `.claude/skills/packetdial/` (scripts: `ns.py`, `ns_client.py`).
**Reads (read-only by default, no gate):**
`domains`, `domain`, `users`, `user`, `phones`, `dids`, `devices`, `callqueues`, `timeframes`,
`sites`, `autoattendants`, `contacts`, `billing`, `addresses`, `smsnumbers`, `blocked-numbers`,
`moh`, `dialrules`, `recording`, `transcriptions`, `cdrs`, `resellers`
All listed reads are live-verified against the production reseller key (2026-06-22).
**Writes (every mutating command is `--confirm`-gated; prints `[DRY RUN]` and exits non-zero
without the flag):**
| Resource | Wrappers | Verification |
|---|---|---|
| Domains | `create-domain`, `onboard-domain` | `onboard-domain` end-to-end verified (vwp.91912.service) |
| Users | `create-user`, `update-user`, `delete-user` | [P] |
| Call queues | `create/update/delete-callqueue`, `add/update/remove-agent` | [V] full lifecycle on `arizonacomputerguru` |
| Time frames | `create/update/delete-timeframe` | [V] full lifecycle on `arizonacomputerguru` |
| Contacts | `create/update/delete-contact` | [V] on `arizonacomputerguru` |
| DIDs | `create-did`, `update-did`, `delete-did` | [P] |
| SIP devices | `create-phone`, `create/update/delete-device` | [P] |
| E911 addresses | `create/update/delete-address` | [P] (create requires `addresses/validate` → pidflo first) |
| Sites | `create-site`, `update-site` | [P] |
| Auto-attendants | `create-autoattendant` | [P] |
| SMS numbers | `create/update/delete-smsnumber` | [P] (requires SMS-provisioned domain) |
| Blocked numbers | `block-numbers`, `unblock-numbers` | [P] (202'd but didn't persist on empty test domain) |
| Music on hold | `create-moh` (TTS), `delete-moh` | [P] (multipart file upload → use `raw`) |
**[V]** = lifecycle-verified on ACG test domain; **[P]** = plumbed per spec, not lifecycle-verified.
**Raw escape hatch:** `ns.py raw <METHOD> <path>` reaches any of the 239 v2 paths. Non-GET methods
still require `--confirm`.
### `onboard-domain` Wrapper
One gated command that runs the OITVOIP "Add a Domain" GUI wizard as 3 API calls:
1. `POST /domains` — creates the domain (Basic + Defaults + Limitations fields).
2. `POST /domains/{domain}/addresses/validate` — validates the E911 address, returns `address-formatted-pidflo` + `emergency-address-id`.
3. `POST /domains/{domain}/addresses` — creates the E911 address with the pidflo from step 2.
Usage: `ns.py onboard-domain --body-file new-client.json --confirm`
The body is the `POST /domains` schema plus an optional `emergency` sub-object carrying the E911
address fields. Omit `limits-max-*` for unlimited seat counts (they default to 0 = uncapped; do not
set them to a sentinel). Omit `dial-plan` (auto-generates named after the domain).
Undo a bad onboard: `ns.py raw DELETE domains/<domain> --confirm`.
### PBX Gotchas
- **`domain-type` stores as `"no"` on create** — the field sent as `"Standard"` came back `"no"` on
`vwp.91912.service`. Fix post-create: `ns.py raw PUT domains/<domain> --body '{"domain-type":"Standard"}' --confirm`.
- **Voicemail user-defaults not in `POST /domains`** — the GUI Defaults tab (Enable VM, Transcription,
Message) has no equivalent in the domain-create schema; how the GUI applies them is unresolved (verify).
- **`email-send-from-address` left blank on create** — must be set separately once the sender mailbox
exists: `ns.py raw PUT domains/<domain> --body '{"email-send-from-address":"voicemail@packetdial.com"}' --confirm`.
ACG's own domain uses `notify@oitvoip.com` as the working pattern.
- **Unlimited seat counts = omit `limits-max-*`** — do not pass 0 or a sentinel; omitting the fields
lets the platform default to uncapped.
- **Timeframe writes are body-discriminated** — the same path handles all operations; the server
selects create/update/delete based on the request body. Entry-bearing types (days-of-week, etc.)
reject creation without their array — create as `always` type first, then convert.
- **`voip.packetdial.com` has no API** — this is the customer-facing white-label portal (Cascades fax
account 28598 lives here). Hitting `/ns-api/*` on this host returns `errors/not_found`.
- **CDR queries are unbounded by default** — always pass `--start`/`--end` and `--limit`.
- **This is the live production PBX.** Confirm the target domain with a read command before any write.
A bad `create-domain` or `delete-user` affects real customers.
---
## Phone Management (Yealink YMCS)
### API
| Field | Value |
|---|---|
| Base URL | `https://us-api.ymcs.yealink.com` (US region; EU = `eu-api.ymcs.yealink.com`; AU = `au-api.ymcs.yealink.com`) |
| Region env | `YMCS_REGION` (us | eu | au; default us) |
| Auth | `POST /v2/token` with HTTP Basic `base64(AccessKeyID:Secret)` + headers `timestamp` (ms) + `nonce` + body `{"grant_type":"client_credentials"}` → bearer token (~24h) |
| TLS | **Requires legacy TLS renegotiation** — handled in-client via `OP_LEGACY_SERVER_CONNECT` SSL context |
| Vault path | `services/yealink-ymcs.sops.yaml``credentials.access_key_id` / `credentials.access_key_secret` |
### Account Tree (verified 2026-06-22)
The ACG AccessKey is the **parent account** — one key covers all client sites as children. No
per-client API keys are needed.
```
Arizona Computer Guru LLC (parent / ACG account)
├── VWP (Valley Wide Plastering)
├── GuruHQ (ACG office)
└── Ace Pick Up Parks
```
### RPS Provisioning Server
| Field | Value |
|---|---|
| Server name | `WL - ACG` |
| URL | `ftp://p.packetdials.net` |
| Purpose | Zero-touch provisioning — phones pull their config from this RPS server on first boot |
### `yealink-ymcs` Skill
Skill location: `.claude/skills/yealink-ymcs/` (scripts: `ymcs.py`, `ymcs_client.py`).
**Reads (live-verified unless noted):**
`sites`, `devices` (21 total; `--site` filter best-effort [P]), `accounts`, `rps-servers`,
`rps-devices`, `device-groups`, `device-configs`, `firmwares`, `models`, `alarms`, `oplogs`
`official-firmwares` returns a non-standard shape (likely requires a model param) — marked [P].
List endpoints auto-paginate (`{skip,limit,autoCount}``{total,data:[]}`); wrappers return
`{total, count, data}` across all pages.
**Writes (all `--confirm`-gated; not lifecycle-verified — no throwaway device to safely test against):**
| Command | Effect |
|---|---|
| `add-devices-by-mac` | Bulk-add phones by MAC address to a site |
| `add-sipaccount` | Push a SIP credential onto a device (the PBX↔phone integration glue) |
| `reboot` | Reboot device(s) by ID |
| `reset` | Factory-reset device(s) — destructive |
| `rps-add` | Register device(s) in RPS for zero-touch provisioning |
| `rps-del` | Remove device(s) from RPS |
**Raw passthrough:** `ymcs.py raw POST /v2/dm/listDevices --body '{...}'` reaches any `/v2/dm/*` or
`/v2/rps/*` endpoint.
### YMCS Gotchas
- **Legacy TLS required.** If a future Python update drops `OP_LEGACY_SERVER_CONNECT`, connections to
YMCS will silently fail. The SSL context with that flag is baked into `ymcs_client.py`.
- **MSYS path conversion on `raw`.** A leading `/v2/...` argument gets rewritten by Git-Bash to
`C:/Program Files/Git/v2/...`. The `raw` command auto-recovers (strips the prefix back to `/v2/`).
Do NOT set `MSYS_NO_PATHCONV=1` — it breaks the vault subprocess and causes credential failures.
Named wrappers (hardcoded paths) are unaffected.
- **`/v2/dm/sipAccounts` is a CREATE endpoint, not a list.** It is wrapped as the gated
`add-sipaccount` write. There is no list-SIP-accounts endpoint in the v2 API.
- **Per-client portal logins exist** (e.g., vault `clients/valleywide/`) even though the API key
covers all clients. The portal admin for the ACG parent account is `admin@azcomputerguru.com`.
---
## Onboarding Pipeline
New VoIP client flow (the two skills together):
```
1. packetdial onboard-domain → PBX domain created + E911 address validated and registered.
2. packetdial create-user → SIP extensions created per user.
3. packetdial create-did → DIDs attached and routed to users.
4. yealink-ymcs add-devices-by-mac → Physical phones added to the client's YMCS site.
5. yealink-ymcs add-sipaccount → NetSapiens SIP credential pushed onto each phone.
6. yealink-ymcs rps-add → (Optional) Phone registered in RPS WL-ACG for zero-touch
on first boot (pulls config from ftp://p.packetdials.net).
```
Result: new client → domain live → phones registered to `pbx.packetdial.com`.
**VWP is the live pilot** (2026-06-22): `vwp.91912.service` on the PBX, 21 devices in YMCS
(fleet partly pending — 16x Yealink T54W plus others). Steps 46 not yet exercised on
production VWP hardware (writes are [P] for YMCS).
**Post-onboard manual steps (gaps vs. full GUI wizard):**
- Fix `domain-type` if it stored as `"no"` (see PBX gotchas above).
- Set `email-send-from-address` once the `voicemail@packetdial.com` / `noreply@packetdial.com`
mailbox exists (pending infra task as of 2026-06-22).
- Apply voicemail user-defaults — mechanism for applying the GUI Defaults tab (Enable VM,
Transcription, Message) via the API is unresolved (verify).
---
## Access
| Credential | Vault Path | Field(s) |
|---|---|---|
| NetSapiens reseller API key | `msp-tools/oitvoip.sops.yaml` | `credentials.api_key` |
| YMCS API AccessKey | `services/yealink-ymcs.sops.yaml` | `credentials.access_key_id`, `credentials.access_key_secret` |
| Yealink portal admin (`admin@azcomputerguru.com`) | `infrastructure/voip-phones.sops.yaml` | (verify field names) |
| VWP per-client YMCS portal | `clients/valleywide/` | (verify entry name) |
Read a field: `bash .claude/scripts/vault.sh get-field <vault-path> <field>`
**NEVER inline raw secrets or API key values** — reference vault paths only.
---
## Known Issues & Quirks
- **`domain-type` stored as `"no"` on domain create (observed 2026-06-22).** Re-PUT the field after
`onboard-domain`. `onboard-domain` wrapper does NOT auto-correct this — it is a platform behavior.
- **Voicemail user-defaults have no `POST /domains` field.** The GUI Defaults tab (Enable Voicemail,
Transcription, Message to Email) is not represented in the domain-create schema. How and where the
GUI applies these defaults is unresolved; set manually post-onboard (verify the mechanism).
- **`email-send-from-address` must be set separately.** Required for voicemail-to-email delivery. The
sender mailboxes (`voicemail@packetdial.com` / `noreply@packetdial.com`) were not yet created as of
2026-06-22. ACG's own domain uses `notify@oitvoip.com` as the working pattern.
- **Blocked-number filters inconclusive on empty domains.** `block-numbers` returned 202 but the
number did not persist on the `arizonacomputerguru` test domain (empty). Verify on a live domain
with real traffic before relying on this feature.
- **YMCS write wrappers are [P] (plumbed, not lifecycle-verified).** No throwaway device was
available to safely test `add-devices-by-mac`, `add-sipaccount`, `reboot`, `reset`, or `rps-add/del`.
Exercise first on a non-critical device (e.g., a phone already due for reprovisioning).
- **Yealink known-bad firmware `96.86.0.20`** is a documented T54W brick-maker. Before any mass
firmware push via YMCS, confirm the active firmware policy is NOT targeting this version.
See `clients/valleywide/docs/yealink-t54w-recovery-procedure.md` for the TFTP recovery procedure.
- **OIT-API.txt plaintext** was present at `C:\Users\guru\Downloads\OIT-API.txt` as of 2026-06-22.
Mike to delete; key is in the vault.
---
## Backlinks
- [[clients/valleywide]] — VWP is the live pilot client; 21 Yealink devices in YMCS,
`vwp.91912.service` on the PBX, Syncro ticket #32375 (New Phone Install) open.
- Skills: `.claude/skills/packetdial/` (`packetdial`) and `.claude/skills/yealink-ymcs/`
(`yealink-ymcs`).