Skip to content

Screening Contexts

Screening contexts implement destination screening — class-of-service / toll restriction for subscriber lines. After a line’s dialed number has been translated and a destination resolved, the screening context decides whether that line is allowed to make the call. A call that is not permitted is rejected with the toll-denied treatment (SIP 403, Q.850 cause 57).

Typical uses: internal-only lobby and courtesy phones, local-only residential class of service, blocking premium-rate prefixes, and time-of-day calling restrictions.

Screening applies to line-originated calls only, and runs:

  • after the translation walk and all digit manipulation — rules match the final, rewritten number, so you write them against one canonical form;
  • before the destination is dialed.

Screening is not applied to:

  • Trunk-originated calls — trunks are interconnects, not subscribers; use translation-context structure to control where inbound trunk traffic can go.
  • Emergency calls — a number matched by a special-number profile bypasses screening entirely. A fully restricted phone always reaches 911.
  • Feature codes — a matched feature code (e.g. *98) short-circuits before translation and screening.
  • Calls that already failed — a no-match or forced treatment keeps its outcome; screening only gates calls that resolved to a real destination.

screening-contexts is a top-level key. Each context is a map of priority keys (numeric strings, evaluated lowest first) to rules; each rule is a match block plus a single permit decision:

screening-contexts:
local-only:
"100":
match:
- ast-pattern: "_1XXX" # on-net extensions
permit: true
"200":
match:
- match-on-call-details:
match-on: call-type
match:
- exact: "local"
permit: true
"300":
match:
- ast-pattern: "_+18[0245678]XXXXXXXX" # toll-free
permit: true

A line profile applies a context to every call its lines originate:

line-profiles:
lobby-phone:
screening-context: local-only
inbound-translation-context: from-lines
...

An empty / omitted screening-context means no screening — the line can dial anything its translation context routes.

  1. Rules are evaluated in priority order, lowest first.
  2. The first rule whose match block matches decides the call — its permit value is final. Later rules are not consulted.
  3. If no rule matches, the call is DENIED.

That last point is the defining property: a screening context is an allowlist that fails closed. You enumerate what a line may call; anything you didn’t anticipate is blocked. This is deliberate anti-fraud posture — a new premium-rate prefix or an unexpected number format is denied by default, not permitted by omission.

Explicit permit: false rules are still useful for carving exceptions out of a broader permit:

screening-contexts:
standard:
# Block premium-rate before the general long-distance permit below.
"100":
match:
- ast-pattern: "_+1900XXXXXXX"
- ast-pattern: "_+1976XXXXXXX"
permit: false
"200":
match:
- regex: "^\\+1"
permit: true

The match block supports the full match engine — exact, regex, ast-pattern, and, always/never, time-of-day, and match-on-call-details. There is no modify and no target: screening never rewrites the number and never routes.

A denied call receives the toll-denied treatment: the caller hears the denial announcement and the call releases with SIP 403 / Q.850 cause 57. The CDR records the treatment; the route-forensics record shows the resolved destination and the screening denial.

Extensions and the operator, nothing else. No PSTN rule at all — everything off-net falls off the end and is denied:

screening-contexts:
internal-only:
"100":
match:
- ast-pattern: "_1XXX"
- exact: "0"
permit: true
line-profiles:
courtesy:
screening-context: internal-only
inbound-translation-context: from-lines
screening-contexts:
residential-local:
"100":
match:
- ast-pattern: "_1XXX" # on-net
permit: true
"200":
match:
- ast-pattern: "_+18[0245678]XXXXXXXX" # toll-free families
permit: true
"300":
match:
- match-on-call-details:
match-on: call-type
match:
- exact: "local"
permit: true

Long-distance upsell is then just a profile change (screening-context: swapped or removed) — no translation edits needed.

Long-distance permitted only during staffed hours; local always permitted:

screening-contexts:
hours-gated-ld:
"100":
match:
- ast-pattern: "_1XXX"
- match-on-call-details:
match-on: call-type
match:
- exact: "local"
permit: true
"200":
match:
- and:
- regex: "^\\+1"
- time-of-day:
days: [mon, tue, wed, thu, fri]
start: "07:00"
end: "18:59"
timezone: America/Chicago
permit: true

After hours, rule 200’s time window doesn’t match, nothing else covers long-distance, and the call is denied.

screening-contexts:
no-premium:
"100":
match:
- ast-pattern: "_+1900XXXXXXX"
- ast-pattern: "_+1976XXXXXXX"
permit: false
"900":
match:
- always: true
permit: true

The trailing always/permit: true converts the context from an allowlist into a blocklist — use this shape only when the line class is genuinely unrestricted apart from the enumerated exceptions.