Namespace Linkages
A namespace linkage is your namespace’s declared relationship with one peer
namespace. Linkages live under the top-level ns-linkages: key, keyed by
peer, and each entry has two independent halves:
- The transfer seam (call time) — whether the peer may hand calls into your namespace, and the translation, caller-ID, and STIR/SHAKEN policy applied when a call crosses the boundary in either direction.
- The inheritance grant (configuration time) — meaningful only when the peer is your direct child: which namespace profile it inherits, which aliases it is granted, and the limits you impose on it.
ns-linkages: child: # applies to every direct child allow-inbound: true inbound-transfer-context: from-tenants inheritance: profile: standard-tenant
0197f9d2-...-8c1a: # a specific child — overrides "child" for it allow-inbound: true inbound-transfer-context: from-tenants caller-id-mode: provided allowed-caller-ids: ["+1415555....", "+1415556...."] inheritance: profile: premium-tenant max-concurrent-calls: 100The most useful mental model: a linkage is a virtual trunk. Your namespace treats a call arriving from a peer exactly the way it treats a call arriving on a SIP trunk — it lands in a translation context you chose, its asserted caller-ID is screened under a trust mode you chose, and your STIR/SHAKEN ingress policy decides what its signature is worth. The peer’s internal structure is irrelevant, just as a carrier’s is.
Linkage keys
Section titled “Linkage keys”An entry’s key names the peer it applies to. Four forms are accepted:
| Key | Applies to |
|---|---|
| a namespace ID | exactly that peer |
a received alias (e.g. pstn) |
the namespace the alias resolves to |
child |
every direct child, unless a specific entry exists for it |
parent |
your direct parent |
At call time the most specific entry wins, in this order: exact-nsid key →
alias key resolving to the peer → child/parent shorthand. The winning
entry is used in its entirety — a specific entry does not blend with the
shorthand entry, so a per-child entry must restate everything it needs.
The permission model
Section titled “The permission model”Each namespace controls its own boundary, and only its own:
- Entry is gated by the receiver. When a call is transferred from
namespace A to namespace B, the switch consults B’s linkage keyed
by A. If no entry matches, or the matching entry does not set
allow-inbound: true, the transfer is refused — the caller receives the route target’s failure treatment (reorderby default, a fast-busy / SIP 503). allow-inbounddefaults to deny. An unconfigured peer cannot enter, and that includes your own children and your own parent. The classic symptom is a child whose on-net calls all work while every off-net call fast-busies: the parent has not yet allowed inbound transfers from that child, so the child’spstnhops are refused at the parent’s door.- Exit needs no permission from your side. There is no “allow-outbound” — if a route target resolves to a peer namespace, the call is offered to it, and the peer’s ACL decides. Your linkage’s outbound fields shape the call on the way out; they do not gate it.
- Admission caps apply at the door. A transfer entering a namespace is
checked against that namespace’s concurrent-call cap (and its partition’s)
before it is accepted; over-limit transfers receive the
all-circuits-busytreatment.
Calls reach a peer namespace through three route shapes, all subject to the
same model: a namespace target (nsid or granted alias), the pstn target
(shorthand for namespace with the granted alias literally named pstn),
and an alias-namespace pointer line (a line
that transfers instead of ringing devices — how a parent delivers DIDs down
into a child).
A single call may chain through at most 8 namespace transfers; deeper chains are treated as a loop and released.
Anatomy of a transfer
Section titled “Anatomy of a transfer”When a call in namespace A resolves to a target in namespace B, the hop proceeds in order:
- ACL — B’s linkage keyed by A is resolved; without
allow-inbound: truethe transfer ends here. - A’s outbound shaping (from A’s linkage keyed by B, if any):
outbound-transfer-context— a translation context run over the dialed number for rewrites only; anytargetit resolves is ignored (the destination is the transfer itself).outbound-caller-id-context— a caller-ID context correcting the presented external caller-ID before hand-off.transmit-passport— folded into the call’s carried egress policy (most restrictive wins), constraining what the eventual trunk hop may emit.
- Admission — B’s namespace/partition concurrent-call caps.
- B’s ingress caller-ID screen — the peer’s asserted external
caller-ID is screened exactly like trunk ingress, in the order
caller-id-mode→inbound-caller-id-context→allowed-caller-ids(details below). A violation is released with theinvalid-caller-idtreatment. - Presentation — if B’s linkage sets
present-external-caller-id, the screened external caller-ID becomes what B’s destination sees; otherwise the original internal caller-ID is preserved across the boundary. - B’s STIR/SHAKEN ingress — the linkage’s
attestation-modeandresign-policyreplace whatever the call carried (see below), inbound verification re-runs under B’s policy, andpassthrough-blockrejects an unsigned arrival. - B’s inbound walk — the call enters B’s
inbound-transfer-contextand routes under B’s configuration from there.
Caller-ID at the seam
Section titled “Caller-ID at the seam”The ingress fields mirror the trunk-profile caller-ID model exactly, applied to the external caller-ID the peer asserts (across a namespace boundary, internal extension identity is meaningless — a child presenting a DID to its parent is treated like a PSTN trunk presenting one):
| Field | Direction | Meaning |
|---|---|---|
caller-id-mode |
ingress | Trust level: fixed, provided (default), or modify |
primary-caller-id |
ingress | The number fixed mode stamps (required with fixed) |
inbound-caller-id-context |
ingress | Rewrite context run under modify mode only |
allowed-caller-ids |
ingress | Allowlist screened after mode/context; empty = unrestricted |
present-external-caller-id |
presentation | Show the (screened) external CID to the destination, instead of preserving the internal CID |
outbound-caller-id-context |
egress | Rewrite applied when you transfer a call out to this peer |
Typical choices: a parent trusts a well-behaved child with
provided + an allowed-caller-ids list pinning the child to its own DIDs;
fixed + primary-caller-id forces everything from a peer to one billing
number; modify normalizes a peer’s formatting before the allowlist screen.
present-external-caller-id is the knob that makes PSTN delivery look
right: when the parent hands an inbound PSTN call down into a child, setting
it on the child’s parent linkage makes the child’s phones display the
real outside caller rather than the parent’s internal identity. Leave it
unset for PBX-to-PBX handoffs where extensions should keep seeing each
other’s internal numbers.
STIR/SHAKEN at the seam
Section titled “STIR/SHAKEN at the seam”A linkage carries the same three knobs as a trunk profile, with the same directional split — see Trunk Profiles for the full semantics:
attestation-mode(ingress) — the attestation level your namespace asserts if it signs a call that entered over this linkage. Required on every linkage once your namespace enables outbound signing.resign-policy(ingress) — the disposition of the signature the call arrived with (passthrough,passthrough-block,resign,keep-sign; defaultkeep-sign).transmit-passport(egress) — which STIR/SHAKEN headers survive when you transfer a call out over this linkage (transmit-full,transmit-verstat,strip).
The crossing itself is a hard trust boundary: whatever attestation intent the call carried inside the peer is discarded at entry and replaced by your linkage’s values. A child can never dictate the attestation level its parent signs with, and an unsigned call never inherits a signed appearance by crossing a boundary.
The inheritance grant
Section titled “The inheritance grant”The inheritance: block is the configuration-time half, read by the system
when a direct child’s configuration is compiled. It is only meaningful
on an entry keyed by a child’s nsid or on the child shorthand — an
inheritance: block on a parent-keyed or alias-keyed entry is ignored.
An nsid-keyed entry carrying a grant must name an actual direct child;
anything else (a grandchild, a sibling, an arbitrary namespace ID) is
rejected at validation.
| Field | Effect on the child |
|---|---|
profile |
The namespace profile whose selections merge into the child |
granted-aliases |
Alias → namespace grants, overlaying the profile’s set. A value is self (you), a namespace UUID, or the name of an alias you received (re-granting it by name) — anything else is rejected at validation |
max-concurrent-calls |
A cap on the child’s concurrent calls (0 = none imposed) |
permitted-edge-ips |
Restricts the edge-proxy values the child may configure — exact IPs/hosts or CIDR blocks |
permitted-sip-realms |
Restricts the child’s sip-realm — each entry permits the realm and its subdomains |
The imposed values are stamped onto the child outside its own document, so the child cannot loosen them in YAML:
- The child’s enforced call cap is
min(child's own max-concurrent-calls, your imposed cap)— a child can restrict itself further, never raise your limit. (Your partition’s account-wide cap still applies above both.) - The
permitted-*lists compose with the partition’s own allow-lists: the child’s chosen value must satisfy every list present. - Restrictions flow down, and a grant can only narrow them. When a grant
omits
permitted-edge-ips/permitted-sip-realms, the child is held to the same permitted lists you are held to. Setting a list explicitly replaces that default for the child, but every entry must fall within your own permitted envelope — the intersection of your partition’s allow-lists and what your own parent permits you. A granted CIDR must lie inside one of your permitted CIDRs, a granted realm inside one of your permitted realms; an entry outside your envelope is rejected when you store the version. No namespace can hand a child endpoints it cannot use itself. - List entries are also checked for well-formedness (a malformed CIDR, an
empty entry) when you store the version, so a bad entry is rejected at
its source rather than mysteriously rejecting the child’s
edge-proxylater.
Worked example: parent and child
Section titled “Worked example: parent and child”The parent accepts calls from every tenant, delivers a DID into one child, and imposes limits:
# ---- parent (carrier / reseller) ----translations: from-tenants: # Tenants hand up E.164; route it out the wholesale route. "100": match: - regex: "^\\+" target: resource-type: route resource-id: wholesale-out failure-treatment: temporary-failure "900": match: - always: true target: resource-type: treatment resource-id: cannot-complete-as-dialed
from-carrier: # DID block owned by tenant1 → pointer line → transfers down. "100": match: - exact: "+14155551000" target: resource-type: line resource-id: tenant1-main
lines: tenant1-main: alias-namespace: 0197f9d2-aaaa-bbbb-cccc-0123456789ab # tenant1's nsid
ns-linkages: child: allow-inbound: true inbound-transfer-context: from-tenants caller-id-mode: provided inheritance: profile: standard-tenant 0197f9d2-aaaa-bbbb-cccc-0123456789ab: # tenant1 gets tighter terms allow-inbound: true inbound-transfer-context: from-tenants caller-id-mode: provided allowed-caller-ids: ["+14155551..."] # pinned to their DID block inheritance: profile: standard-tenant max-concurrent-calls: 50 permitted-sip-realms: [tenant1.example.net]The child allows its parent in (for DID delivery) and presents outside callers honestly:
# ---- tenant1 (child) ----translations: from-parent: "100": match: - ast-pattern: "_+1415555....." modify: operations: - isolate-digits: "7:" # +14155551234 → extension 1234 target: resource-type: line resource-id: "!dialedNum" not-found-treatment: unallocated-number "900": match: - always: true target: resource-type: treatment resource-id: unallocated-number
ns-linkages: parent: allow-inbound: true inbound-transfer-context: from-parent present-external-caller-id: trueOutbound, tenant1’s lines dial resource-type: pstn in their (inherited)
translation context; the granted pstn alias resolves to the parent, the
parent’s child linkage admits the call into from-tenants, and the
allowlist on the nsid-keyed entry holds tenant1 to its own DIDs.
Managed-mode namespaces
Section titled “Managed-mode namespaces”In a managed-authority namespace, linkages are not authored in YAML —
they are individual resources created and updated through the
/v1/ns/{nsid}/resources/linkages/{peer} REST endpoints, with the same
fields and the same semantics. Everything above about keys, permissions, and grants applies
unchanged.