packetdial: build out + document the skill against the live NetSapiens v2 API

Key is now provisioned + live-verified, so grounded the skill in the real spec (RTFM):
- Mapped the OpenAPI surface (v44.4.10, 239 paths / 354 ops) — capability map added to
  SKILL.md (what the platform exposes vs what's wrapped vs raw-only).
- Added 6 live-verified read wrappers (ns.py + ns_client.py): callqueues, timeframes,
  sites, contacts, autoattendants, billing (domain limits/usage).
- Replaced the stale "not yet provisioned" credentials section with the live status
  (vaulted nsr_ reseller key, key-id nsr_hSGUB5Wo, scope Reseller 91912.service, RW) +
  the pbx.packetdial.com vs api.ucaasnetwork.com hostname note + override.
- api.md history updated. Writes remain gated behind --confirm; everything unwrapped
  reachable via `raw`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 07:36:12 -07:00
parent 567986fa49
commit d75c367bf7
4 changed files with 98 additions and 27 deletions

View File

@@ -1,6 +1,6 @@
---
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."
description: "Manage the ACG PacketDial/OITVOIP hosted VoIP via the NetSapiens API v2 (pbx.packetdial.com). List/inspect domains, users, devices, DIDs, call queues, time frames, sites, auto-attendants, contacts, and domain billing/limits; pull CDRs; provision domains/users/SIP/numbers (writes gated --confirm; read-only default). Reseller-scoped key live + vaulted. Triggers: packetdial, oitvoip, netsapiens, voip domain/user/extension, call queue, auto attendant, provision phone, add did, CDR. Live production PBX."
---
@@ -23,35 +23,23 @@ every write (create / update / delete) is gated behind `--confirm`.
- 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)
## Credentials — PROVISIONED + LIVE-VERIFIED (2026-06-22)
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:
The reseller API key is vaulted at **`msp-tools/oitvoip.sops.yaml`** →
`credentials.api_key` (static `nsr_` bearer key). Live-verified via `ns.py whoami`:
key-id `nsr_hSGUB5Wo`, **user-scope Reseller** (`91912.service`), `user/domain: *`
(sees every domain under the ACG reseller territory), **`readonly: no`** (read-write —
so the `--confirm` gate on writes matters), `can-create-keys: no`.
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 id>
client_secret: <client secret>
username: <portal user@domain>
password: <portal 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
(`client_id`/`client_secret`/`username`/`password`). Env overrides exist for quick
testing without touching the vault.
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.
> The key's own docs reference `https://api.ucaasnetwork.com/ns-api/...`; the skill
> targets `pbx.packetdial.com/ns-api/v2` and the key works there directly. They are the
> same NetSapiens platform behind two white-label hostnames — if a future endpoint 403s
> on `pbx.`, override with `PACKETDIAL_API_BASE_URL=https://api.ucaasnetwork.com/ns-api/v2`.
## Running the CLI
@@ -69,10 +57,19 @@ bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py user <domain> <user>
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py phones <domain> # SIP devices registered in a domain
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py dids <domain> # phone numbers (DIDs) on a domain
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py devices <domain> <user>
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py callqueues <domain> # ACD call queues (agents, live queued count)
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py timeframes <domain> # time-based routing (business hours / holidays)
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py sites <domain> # multi-site definitions
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py autoattendants <domain> # IVR menus
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py contacts <domain> # shared address book
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py billing <domain> # limits + current usage counts
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py cdrs --domain <domain> --start 2026-06-01 --end 2026-06-02
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py resellers
```
All read wrappers above are **live-verified** against the production reseller key
(2026-06-22).
## Writes (gated)
Every mutating command prints a `[DRY RUN]` line and exits non-zero unless you
@@ -97,6 +94,31 @@ bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py raw GET domains/acme/users
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" ns.py raw POST domains/acme/users --body '{...}' --confirm
```
## API capability map (RTFM — spec v44.4.10, mapped 2026-06-22)
The live OpenAPI spec is **NetSapiens API v2 v44.4.10****239 paths / 354 operations**
(GET 139 · POST 87 · PUT 68 · DELETE 50 · PATCH 10). Almost everything is nested under
`/domains/{domain}/...`. What the platform exposes, and how this skill surfaces it:
| Resource (`/domains/{domain}/…`) | Ops | Skill wrapper | Notes |
|---|---|---|---|
| `users` (+ `/devices`, answerrules, voicemail, contacts under a user) | R/W, biggest surface | `users`, `user`, `devices`, `create/update/delete-user` | per-user devices live at `users/{u}/devices` |
| `phonenumbers` (DIDs + dial-rule routing) | R/W | `dids`, `create-did` | |
| `phones` (SIP devices, MAC provisioning) | R/W | `phones`, `create-phone` | |
| `callqueues` (ACD: agents, dispatch, live stats) | R/W + PATCH | `callqueues` (read) | writes via `raw` |
| `timeframes` (business-hours / holiday schedules) | R/W | `timeframes` (read) | writes via `raw` |
| `autoattendants` (IVR menus) | R/W | `autoattendants` (read) | |
| `sites` (multi-site) | R/W | `sites` (read) | |
| `contacts` (domain address book) | R/W | `contacts` (read) | |
| `addresses` (E911) · `moh` (music on hold) · `dialplans` · `msg`/`smsnumbers` (SMS) · `connections` · `number-filters` | R/W | `raw` | not yet wrapped |
| `cdrs` · `calls` · `recordings` · `transcriptions` · `queuedcall` | read | `cdrs`; rest via `raw` | call data |
| `billing` (limits + current counts) | read | `billing` | per-domain quota snapshot |
| Top-level: `domains`, `resellers`, `apikeys`, `subscriptions`, `routes`, `configurations`, `certificates`, `templates`, `jwt`, `tokens` | R/W | `domains`/`domain`/`resellers`; rest via `raw` | platform/admin |
**Everything not wrapped is reachable via `raw <METHOD> <path>`** (non-GET gated by
`--confirm`). The local spec copy used for this map: `.claude/tmp/ns-openapi.json` (refetch
from `pbx.packetdial.com/ns-api/webroot/openapi/openapi.json`).
## Standard provisioning flow (new customer)
1. `create-domain` -> dial plan auto-generates