Signals Docs
SIP Trunks

Outbound policies

An outbound policy is a named set of rules for calls your PBX places to the phone network. It says which countries and number prefixes may be called, how many calls may run, how much may be spent in a day, and whether calls are recorded. You write a policy once and attach it to as many trunks as you like. A policy is live as soon as you create it, and every change you save reaches every attached trunk within a minute.

A trunk with no policy refuses every outbound call.

Policy IDs start with pol_, for example pol_01992f3b-0a27-7f13-8c4e-3b9d6a1e5f20.

What a policy controls

You create a policy with POST /v1/outbound-policies and replace its settings with PATCH /v1/outbound-policies/{policy_id}. Both take the same body, and every field is required:

JSONCode
{ "name": "office-calls", "concurrency": 20, "cps": 3, "max_call_duration_seconds": 3600, "max_destination_rate_minor": 15000, "daily_spend_limit_minor": 5000000, "recording_mode": "off", "recording_channels": 2, "recording_retention_days": 30 }
FieldMeaning
nameLowercase letters, digits and hyphens, up to 63 characters. Unique in your organization.
concurrencyMost calls active at once across every trunk using the policy, counting inbound and outbound calls, or null for no policy-level cap.
cpsMost new outbound calls per second, or null. Inbound calls are not refused for CPS.
max_call_duration_secondsLongest call allowed, or null.
max_destination_rate_minorHighest per-unit rate a destination may cost, as an integer of minor units (kobo for NGN), or null.
daily_spend_limit_minorMost that calls under the policy may spend in a day, as an integer of minor units, or null.
recording_modeoff or all.
recording_channelsAlways 2: each party on its own channel.
recording_retention_days30, 90 or 365.

A policy's currency is always NGN. Money is an integer of minor units, so 5000000 is ₦50,000.00. Read it as an exact integer, not a floating-point number, to avoid rounding.

Signals checks policy limits together with the limits on the trunk, the subaccount and your organization. A call must fit within all of them. The policy's ceilings object reports the saved value of each limit. ceilings.daily_spend also shows used_today_minor and resets_at.

Recording

Setting recording_mode to all records every outbound call under the policy. Recording needs both of the following:

  • Your organization has accepted the recording terms in the console. Without that, the request fails with 409 recording_consent_required.
  • Your project's price book prices recording.

The policy's recording.applied field tells you whether recording is in effect.

Destination rules

Destination rules decide which numbers your PBX may call. Each rule allows or denies one country or one prefix:

  • A country rule names an ISO 3166-1 alpha-2 code, such as NG or GH. It matches that country's calling code. Countries that share a calling code, such as the United States and Canada on +1, have no code of their own, so use prefix rules for them.
  • A prefix rule names the leading E.164 digits without the +, such as 233 for Ghana or 2348091 for a range of Nigerian mobile numbers. A prefix is 1 to 15 digits and cannot start with 0.

GET /v1/destination-countries lists every country a rule can name, with its iso_code, calling_code, continent and shared_code. A country with "shared_code": true cannot have a country rule; use a prefix rule instead.

A new policy starts with one rule that allows Nigeria (NG).

How rules are evaluated

For each call, the most specific matching rule wins: a longer prefix beats a shorter one. When an allow rule and a deny rule are equally specific, the deny rule wins. A number no rule allows is refused.

For example, with these rules:

RuleEffect
Country NG (calling code 234)Allow
Prefix 23480912Deny

a call to +2348034128890 is allowed by NG, and a call to +2348091234567 is refused because the deny rule 23480912 is more specific.

Change rules

Send changes to PATCH /v1/outbound-policies/{policy_id}/destination-rules with If-Match and Idempotency-Key. The policy ETag is its revision as a quoted integer, such as "1". Each change has an op of allow, deny or remove, a kind of country or prefix, a value, and an optional label of 1 to 80 characters:

TerminalCode
curl -X PATCH "$SIGNALS/outbound-policies/$POLICY_ID/destination-rules" \ -H "Authorization: Bearer $SIGNALS_API_KEY" \ -H "Content-Type: application/json" \ -H 'If-Match: "1"' \ -H "Idempotency-Key: 1f9c3e57-6a2d-4b08-9e41-c7d5a0b3f862" \ -d '{ "changes": [ { "op": "allow", "kind": "country", "value": "GH", "label": "Ghana" }, { "op": "deny", "kind": "prefix", "value": "23480912", "label": "Blocked range" } ] }'

The response is the policy with its updated rules (trimmed), and a new ETag of "2":

JSONCode
{ "id": "pol_01992f3b-0a27-7f13-8c4e-3b9d6a1e5f20", "revision": 2, "destination_rules": { "allowed": [ { "match": "GH", "kind": "country", "label": "Ghana" }, { "match": "NG", "kind": "country", "label": "Nigeria" } ], "denied": [ { "match": "23480912", "label": "Blocked range", "source": "customer", "case_ref": null } ] } }
  • Send 1 to 250 changes per request. A destination may appear only once in a request.
  • Changes are keyed by kind and value, so sending the same change twice leaves one rule.
  • Allowed rules also show rate_minor, rate_unit and the billing increments from your project's price book.
  • A denied rule with "source": "containment" was added by Signals fraud containment and carries a case_ref. You cannot remove it yourself.

Rule changes are live once saved. Calls already in progress keep the terms they started with; new calls use the new rules.

Attachments

An attachment links a policy to a service, such as a trunk. A service has at most one policy at a time.

Attach a policy to a trunk

You can attach a policy when you create the trunk, with outbound_policy_id in the create request. To attach or replace it later, use the trunk's ETag:

TerminalCode
curl -X PUT "$SIGNALS/trunks/$TRUNK_ID/outbound-policy" \ -H "Authorization: Bearer $SIGNALS_API_KEY" \ -H "Content-Type: application/json" \ -H 'If-Match: "2"' \ -d '{ "policy_id": "pol_01992f3b-0a27-7f13-8c4e-3b9d6a1e5f20" }'
  • The response is 200 OK with the trunk, its policy set to the new policy, and a new ETag.
  • Send { "policy_id": null } to remove the policy.
  • The change reaches the network within a minute.

List attachments

GET /v1/outbound-policies/{policy_id}/attachments lists the services using a policy, with service_type, service_id, service_name, attached_at, calls_in_flight and an apply_state of applied or pending. A pending attachment is a saved change that has not reached the network yet.

Attachment IDs start with pat_. To move a trunk to another policy, attach the replacement with PUT /v1/trunks/{trunk_id}/outbound-policy. To remove a trunk's policy, send { "policy_id": null } there; the trunk then refuses outbound calls.

Delete a policy

DELETE /v1/outbound-policies/{policy_id} with Idempotency-Key archives the policy and returns 204 No Content. It returns 409 outbound_policy_attached while any service is attached. Replace the policy on every trunk first, or send ?detach=true to detach every service in the same request. Detached trunks then refuse outbound calls.

Errors you may see

Status and codeCause
409 outbound_policy_name_conflictAnother policy has this name.
409 recording_consent_requiredrecording_mode is all but your organization has not accepted the recording terms.
409 outbound_policy_attachedThe policy is attached to a service and cannot be deleted without detach=true.
409 idempotency_conflictThe Idempotency-Key was used before with a different body.
412 outbound_policy_revision_conflictThe If-Match ETag is stale. Read the policy again.
428 precondition_requiredThe request has no If-Match header.
422 invalid_outbound_policyA field is invalid, for example a prefix starting with 0 or a retention of 60 days.
Last modified on