--- name: packetdial description: "Manage the ACG PacketDial/OITVOIP hosted VoIP via the NetSapiens API (pbx.packetdial.com). List/inspect domains, users, devices, DIDs, resellers; pull CDRs; provision domains/users/SIP/numbers (writes gated --confirm; read-only default). Triggers: packetdial, oitvoip, netsapiens, voip domain/user/extension, provision phone, add did, CDR. Live production PBX." --- # PacketDial / NetSapiens (OITVOIP) Skill Standalone CLI client for the NetSapiens SNAPsolution **API v2** that backs ACG's hosted-VoIP offering through OITVOIP / PacketDial. Read-only by default; every write (create / update / delete) is gated behind `--confirm`. ## The two hostnames (important) | Host | What it is | API? | |---|---|---| | `voip.packetdial.com` | Customer-facing white-label portal / UC & fax dashboard (e.g. Cascades fax account **28598**). Login-gated UI. | **No** | | `pbx.packetdial.com` | Reseller PBX platform — NetSapiens v44.4. | **Yes** — this skill targets it | - API base: `https://pbx.packetdial.com/ns-api/v2` - Token endpoint: `https://pbx.packetdial.com/ns-api/v2/tokens` - Live OpenAPI spec: `https://pbx.packetdial.com/ns-api/webroot/openapi/openapi.json` - Live Swagger UI: `https://pbx.packetdial.com/ns-api/openapi` - Vendor docs: https://docs.ns-api.com/ (login) and https://voipdocs.io/oitvoip-access-platform-apis ## Credentials — ONE-TIME SETUP (not yet provisioned) As of this skill's creation **no API key exists yet** — the vault entry `msp-tools/oitvoip.sops.yaml` is empty/absent, so every command will fail with a clear "No credentials found" error until you do this once: 1. Log into `pbx.packetdial.com` -> **Admin > API Keys** and create a reseller-scoped key (prefix `nsr_`). If self-service key creation is not available, reply to **Darwin Escaro (OITVOIP)** for reseller OAuth client credentials. 2. Store it in the SOPS vault. Preferred (static bearer key): ``` # msp-tools/oitvoip.sops.yaml credentials: api_key: nsr_xxxxxxxxxxxxxxxx ``` Or, for OAuth2 password-grant credentials: ``` credentials: client_id: client_secret: username: password: ``` 3. That's it — the client auto-detects which shape is present. The client never hardcodes secrets. Resolution order: `PACKETDIAL_API_KEY` env -> `PACKETDIAL_CLIENT_ID`+friends env -> vault `credentials.api_key` -> vault OAuth fields. Env overrides exist for quick testing without touching the vault. ## Running the CLI This machine's Python launcher is `py` (per identity.json); `python` / `python3` also work. Run from the scripts dir so the two modules resolve. ```bash cd C:/claudetools/.claude/skills/packetdial/scripts bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py status # API version + authenticated key identity bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py domains # list all domains bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py domain # one domain's config bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py users # users / extensions in a domain bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py user bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py phones # SIP devices registered in a domain bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py dids # phone numbers (DIDs) on a domain bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py devices bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py cdrs --domain --start 2026-06-01 --end 2026-06-02 bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py resellers ``` ## Writes (gated) Every mutating command prints a `[DRY RUN]` line and exits non-zero unless you pass `--confirm`. Bodies are raw JSON matching the NetSapiens v2 schema. ```bash bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py create-domain --body '{"domain":"acme","description":"Acme Inc"}' --confirm bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py create-user acme --body '{"user":"101","name-first-name":"Jane"}' --confirm bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py create-phone acme --body '{...}' --confirm bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py create-did acme --body '{"phonenumber":"15205551234"}' --confirm bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py update-user acme 101 --body '{"name-last-name":"Doe"}' --confirm bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py delete-user acme 101 --confirm ``` ## Raw escape hatch (any of the 239 v2 paths) The named commands cover the common surface; for anything else, hit the path directly. Non-GET methods still require `--confirm`. ```bash bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py raw GET domains/acme/users/101/answerrules bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py raw POST domains/acme/users --body '{...}' --confirm ``` ## Standard provisioning flow (new customer) 1. `create-domain` -> dial plan auto-generates 2. `create-user` per extension 3. `create-phone` per SIP device (MAC-provisioned) 4. `create-did` to attach DIDs and route them to users 5. Log the work back to the Syncro ticket ## Notes - This is the LIVE production reseller PBX. A bad `create-domain` or `delete-user` affects real customers — confirm the target domain first with a read command before any write. - CDR queries can be large; always pass `--start`/`--end` and a `--limit`. - Reference detail (auth shapes, full endpoint inventory) lives in `references/api.md`.