Skip to content

Namespace & Tenant Administration

The namespace overview covers what a namespace is, the profiles and linkages pages cover how namespaces relate, and the configuration pages cover what’s inside one. This page is about the objects themselves: creating tenants, navigating the tree, and — carefully — removing them. Conventions as usual: $VSS / $TOKEN / $NSID on the curl side, an active profile on the vsscli side.

Every namespace is identified by a UUID and carries a small set of head metadata:

Field Meaning
nsid The namespace UUID — its identity everywhere in the API
parent_id The parent namespace (empty for a root namespace)
partition_id The partition (customer account) that owns it
running_version / latest_version The two configuration pointers (lifecycle)
owner The user who created it
last_modified Last head change

Three of these are immutable for the namespace’s lifetime: the parent (a namespace is never re-grafted elsewhere in the tree), the partition, and the resource-authority mode (below). If any of them needs to be different, the path is a new namespace.

Operation Requires Held by
Read head, parent, children, tree namespace:read on the namespace Every switch role, down to viewer and noc-viewer
List a partition’s namespaces namespace:list on the partition Partition-scoped grants only — partition-admin, tenant-provisioner, and partition-scoped ops/read roles
Create a root namespace namespace:create on the partition partition-admin, tenant-provisioner (partition-scoped)
Create a child namespace namespace:create-child on the parent — no partition authority involved partition-admin, namespace-admin, provisioner
Delete, flush call status namespace:delete on the namespace partition-admin only

Two boundaries worth internalizing: a namespace-scoped admin cannot enumerate the partition (use tree on your own subtree instead), and deletion is deliberately withheld from namespace-admin — a tenant admin can do anything inside their subtree but cannot destroy it; that stays with the partition.

Terminal window
# Head metadata — running vs latest tells you if a change is staged
curl -s "$VSS/v1/ns/$NSID" -H "Authorization: Bearer $TOKEN"
# Structure
curl -s "$VSS/v1/ns/$NSID/parent" -H "Authorization: Bearer $TOKEN"
curl -s "$VSS/v1/ns/$NSID/children" -H "Authorization: Bearer $TOKEN"
curl -s "$VSS/v1/ns/$NSID/tree" -H "Authorization: Bearer $TOKEN" # nested subtree
# Partition-wide inventory (200/page; pass ?cursor= from next_cursor)
curl -s "$VSS/v1/pt/$PTID/ns" -H "Authorization: Bearer $TOKEN"
# Immutable resource-authority mode
curl -s "$VSS/v1/ns/$NSID/resource-authority" -H "Authorization: Bearer $TOKEN"

Most vsscli commands target the profile’s active namespace. vsscli ns use <nsid> switches it, and profile-local aliases save you the UUID gymnastics:

Terminal window
vsscli ns alias add acme-corp 0197f9d2-…
vsscli ns use acme-corp
vsscli ns tree acme-corp
vsscli ns alias list

A create request carries the namespace’s initial configuration document as its body — the same YAML you’d config push, validated by the same compiler (a child’s document compiles against its parent’s running config, so inherited references resolve). The response returns the new nsid and the initial version’s ULID.

The initial version is stored, not running: like any other configuration version it processes no calls until someone applies it. That two-step is useful — provisioning automation can create the tenant, let a human review, then promote.

Terminal window
# Root namespace (a new tenant at the top of your partition)
curl -s -X POST "$VSS/v1/ns" \
-H "Authorization: Bearer $TOKEN" --data-binary @tenant.yaml
# → 201 {"success":true,"nsid":"…","new_version":"01JD…"}
# Root namespace, naming the partition explicitly (required when your
# token holds grants on more than one partition)
curl -s -X POST "$VSS/v1/ns?partition=$PTID" \
-H "Authorization: Bearer $TOKEN" --data-binary @tenant.yaml
# Child namespace — the partition comes from the parent; only
# create-child authority on the parent is checked
curl -s -X POST "$VSS/v1/ns?parent=$PARENT_NSID" \
-H "Authorization: Bearer $TOKEN" --data-binary @customer.yaml
# Managed resource authority (immutable — decide now)
curl -s -X POST "$VSS/v1/ns?authority=managed" \
-H "Authorization: Bearer $TOKEN" --data-binary @tenant.yaml
# Then promote the initial version when ready
curl -s -X POST "$VSS/v1/ns/$NEW_NSID/config/latest/apply" \
-H "Authorization: Bearer $TOKEN"

?authority= is document (default — resources authored in the YAML) or managed (lines/trunks/ring-groups managed through the resources API; see the overview). It cannot be changed later — switching modes means a new namespace.

Where the new namespace lands depends on the create shape. A child inherits its parent’s partition — the request is authorized purely by create-child on the parent, which is how a provisioner or namespace-admin onboards customers without holding any partition-level rights (naming both ?partition= and ?parent= together is rejected as contradictory). A root create resolves the partition from your token’s partition-scoped grant; if the token holds grants on several partitions, name the target with ?partition= / --partition — the parameter is a disambiguator, not an escalation, so naming a partition you hold no grant on is simply denied.

Deletion is a recursive cascade with no undo: the namespace, every descendant namespace, all their configuration history, users, voicemail boxes (including stored media), devices, and call records go. Because of that, the API refuses a bare DELETE and demands an explicit handshake:

  1. DELETE without a confirmation body → 412 with an impact preview: how many descendants, users, mailboxes, and devices would be destroyed, plus an example_body.
  2. Re-send with the confirmation — a JSON body naming the exact nsid (confirm_id) and a current unix timestamp (confirm_timestamp, accepted within one hour of server time). This forces the client to have seen the target, not just matched a URL pattern.
  3. Live calls anywhere in the subtree → 409, listing the namespaces with calls up. Wait for them to end — or see the force path below.
Terminal window
# Step 1: request the preview (this is safe — nothing is deleted)
curl -s -X DELETE "$VSS/v1/ns/$NSID" -H "Authorization: Bearer $TOKEN"
# → 412 {"title":"Destructive action requires confirmation",
# "descendants":3,"users":12,"vm_boxes":40,"peers":55,
# "example_body":{"confirm_id":"…","confirm_timestamp":…}}
# Step 2: confirm
curl -s -X DELETE "$VSS/v1/ns/$NSID" \
-H "Authorization: Bearer $TOKEN" \
-d "{\"confirm_id\":\"$NSID\",\"confirm_timestamp\":$(date +%s)}"
# → 204

The live-call guard exists to stop you deleting a tenant mid-call. But call-tracking rows can occasionally wedge (they normally expire on their own within their TTL), leaving a namespace that refuses deletion with a 409 for a call that ended long ago. Two escape hatches, both gated on the same namespace:delete authority:

Terminal window
# Clear stuck tracking rows — one call by ULID, or all of them
curl -s -X POST "$VSS/v1/ns/$NSID/flush-call-status" \
-H "Authorization: Bearer $TOKEN" -d '{"call":"all"}'
# Or let the cascade flush them itself: force bypasses ONLY the
# live-call gate — the confirmation body is still required
curl -s -X DELETE "$VSS/v1/ns/$NSID?force=true" \
-H "Authorization: Bearer $TOKEN" \
-d "{\"confirm_id\":\"$NSID\",\"confirm_timestamp\":$(date +%s)}"
Response Meaning Do
412 on DELETE Missing/stale confirmation — the body carries the impact preview Review the counts, re-send with confirm_id + fresh confirm_timestamp
409 active calls on DELETE Live calls somewhere in the subtree (listed in the body) Wait for them to end; if provably stuck, flush-call-status or --force
401 on a root create Token has no partition-scoped grant Root creation needs partition-level authority; child creation only needs create-child on the parent
400 ambiguous partition on a root create Token holds grants on several partitions Name the target: ?partition= / --partition
400 partition + parent together The two parameters contradict — a child inherits its parent’s partition Drop ?partition= from child creates
400 validation errors on create The initial configuration failed compilation Same fix loop as a config push
404 on any read The nsid doesn’t exist Check the id (ns list / ns tree)
401 on any read The namespace exists but your grants don’t reach it See how requests are authorized