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

16 KiB
Raw Blame History

type, name, display_name, last_compiled, compiled_by, sources, backlinks
type name display_name last_compiled compiled_by sources backlinks
system packetdial PacketDial (ACG VoIP / NetSapiens via OIT) 2026-06-22 GURU-5070/claude-main
.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
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.yamlcredentials.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.

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
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.yamlcredentials.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

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.

  • 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).