# Outbound pre-call check & call-provenance — do-not-contact scrub + spoofing defence

- Category: Outbound & compliance
- Published: June 5, 2026 · Updated: June 18, 2026
- Canonical: https://numbers.online/docs/integrations/precall-call-provenance

Two endpoints for outbound voice/SMS senders — dialers, SBCs, and AI voice agents. Run a **pre-call scrub** on the number you are about to dial, and **enrol** your own verified business numbers so that calls spoofing your caller ID don't get charged to your reputation.

> **Positioning — read this first.** The pre-call check is a **supplementary signal**, never a consent grant: a `NO_MATCH` result is **not** permission to call — you remain responsible for your own lawful basis. Enrolment records who you actually dialled so the *absence* of a record becomes evidence a reported call wasn't yours; it is **not** a compliance determination, a block, or a verdict on any call.

> **Paths.** The canonical spellings are now `POST /api/v1/outbound/lookup` and `POST /api/v1/outbound/enroll` — the outbound mirror of `/api/v1/inbound/lookup`. The `/api/v1/precall/*` paths used throughout this guide are **permanent aliases** of the same handlers; everything below works unchanged on either spelling. Your verified numbers are also listable at `GET /api/v1/account/phones`, with `PATCH /api/v1/account/phones/{e164}` as a resource-shaped enrolment toggle.

---

## 1. What it is for

- **Pre-call scrub** (`POST /api/v1/precall/lookup`) — right before you dial a number, check whether its verified owner has asked not to be marketed to (a consent-first preference we hold, **not** the official/government do-not-call registry, which remains yours to check), read a supplementary government/licensed compliance signal where a partner is configured, read an outbound **dial-risk** signal (the cost/abuse risk of dialing the destination), and read an indicative **cost-to-reach** estimate (retail per-minute voice and per-message SMS list prices). Free, bundled, fail-open.
- **Call-provenance** — when the calling number (`from`) is one of your own **enrolled** verified business numbers, the pre-call check records a short-lived, hashed `(from → to)` edge. If a complaint later arrives about a call you never placed (because someone spoofed your number), the missing edge distinguishes your real, attested calls from the spoofed ones. The spoofed-call report is then treated as a supplementary signal about the spoofing and withheld from your own risk read — so the business whose identity was forged isn't the one penalised.

Both endpoints require an API key with the `precall` use case. Every self-service key has it by default.

---

## 2. Pre-call check

```http
POST /api/v1/precall/lookup
X-API-Key: <your key>
Content-Type: application/json

{
  "from": "+14155550100",            # your calling number (E.164)
  "to":   "+14155552671",            # the number you are about to dial (E.164)
  "context": "outbound_voice"        # outbound_voice | outbound_sms — picks the suppression list scrubbed
}
```

Response:

```json
{
  "schema_version": "2026-06-23",
  "to": "+14155552671",
  "dnc": "NO_MATCH",                  // SUPPRESS | NO_MATCH | UNKNOWN
  "dnc_channel": "voice",             // which suppression list answered: voice | sms (from `context`)
  "dnc_note": "Supplementary signal only. NO_MATCH is not consent; you remain responsible for your own lawful basis to call.",
  "compliance": { /* supplementary compliance signal, where a licensed provider is configured */ },
  "dial_risk": {                      // outbound cost/abuse risk of dialing `to` (supplementary)
    "risk": 6,                        // 0–100
    "level": "low",                   // low (<40) | medium (40–69) | high (≥70)
    "structural_type": "intl_premium",// what `to` resolved to (null if no range matched)
    "reason_codes": ["structural:intl_premium"],
    "note": "Supplementary signal: low-confidence estimate of the cost/abuse risk of DIALING this destination …"
  },
  "cost_estimate": {                   // indicative retail cost to reach `to`; both channels, null where uncovered
    "currency": "USD",
    "note": "Supplementary signal: indicative RETAIL price to reach this destination …",
    "voice": {                         // priced per minute (null if no voice deck covers `to`)
      "unit": "per_minute",
      "min_usd": "0.0072", "avg_usd": "0.0146", "max_usd": "0.32",
      "network_type": "mobile",        // the line-type filter applied (null = unfiltered)
      "providers": 3,                  // distinct providers behind the aggregate
      "rate_count": 5,
      "international": { "min_usd": "0.013", "avg_usd": "0.018", "max_usd": "0.32" },
      "local": { "min_usd": "0.0072", "avg_usd": "0.0072", "max_usd": "0.0072" },
      "breakdown": [                   // named, displayable providers only (cheapest avg first)
        { "name": "DIDWW", "domain": "didww.com", "min_usd": "0.0072", "avg_usd": "0.0072", "max_usd": "0.0072",
          "international": null, "local": { "min_usd": "0.0072", "avg_usd": "0.0072", "max_usd": "0.0072" } }
      ]
    },
    "sms": {                           // priced per message (null if no SMS deck covers `to`)
      "unit": "per_message",
      "min_usd": "0.0667", "avg_usd": "0.0998", "max_usd": "0.2575",
      "network_type": "mobile", "providers": 2, "rate_count": 3,
      "person": { "min_usd": "0.0667", "avg_usd": "0.0667", "max_usd": "0.0667" },        // P2P decks
      "application": { "min_usd": "0.1329", "avg_usd": "0.1329", "max_usd": "0.1329" },    // A2P decks
      "breakdown": [ { "name": "DIDWW", "domain": "didww.com", "min_usd": "0.0667", "avg_usd": "0.0998", "max_usd": "0.1329",
        "person": { "min_usd": "0.0667", "avg_usd": "0.0667", "max_usd": "0.0667" },
        "application": { "min_usd": "0.1329", "avg_usd": "0.1329", "max_usd": "0.1329" } } ]
    }
  },
  "enrolled": true,                   // is `from` an enrolled number of yours?
  "provenance_recorded": true,        // was a provenance edge written for this call?
  "provenance_note": "…",
  "edge_id": "nol_edge_…",            // present only when an edge was recorded
  "ttl_seconds": 604800               // edge lifetime (7 days)
}
```

- **`dnc`** — the **first-party do-not-contact preference** of the destination's verified owner, held by Numbers Online — **not** the government/licensed do-not-call registry (that is the separate `compliance` field). `SUPPRESS` (the verified owner asked not to be marketed to — remove them), `NO_MATCH` (no suppression on record — **not** consent), or `UNKNOWN` (not resolvable). The scrub is **per channel**: `context: "outbound_sms"` is answered from the SMS suppression list, anything else from voice (`dnc_channel` echoes which). Channel opt-ins are independent — the same number can be `SUPPRESS` for voice and `NO_MATCH` for SMS, or the reverse. (Before 2026-06-11 every scrub was answered from the voice list regardless of `context`.)
- **`dial_risk`** — a supplementary read on the *cost/abuse* risk of dialing `to` (separate from `dnc`, which is about the owner's marketing preference). `level` bands the `risk` score (0–100) into `low` / `medium` / `high`; `structural_type` tells you *why* — e.g. `intl_premium` (a +1 number that looks domestic but routes international/high-cost), `premium_prs` (premium/special-rate), `satellite`, `intl_network`, or `unallocated_dialable`. `reason_codes` lists the legible drivers. It is a **low-confidence signal, never a block verdict** — you decide whether to dial. `null` if scoring failed (treat as no signal).
- **`cost_estimate`** — an indicative read on what it *costs* to reach `to`, distinct from `dial_risk` (risk points) — actual retail money. We price **both channels regardless of `context`**: `voice` is per minute, `sms` per message; each is `null` when no rate deck covers the destination, and the whole object is `null` when neither does. All amounts are decimal **USD strings** (money is integer micro-USD internally). Each channel carries a cross-provider `min`/`avg`/`max`, the channel's lane splits (voice → `international`/`local`; SMS → `person` (P2P) / `application` (A2P)), and a `breakdown[]` of named, publicly-displayable providers cheapest-average first (anonymous sources stay folded into the aggregate). `network_type` is the line-type filter applied (e.g. `mobile`, `premium`), `null` when unfiltered. These are **published list prices aggregated across providers — not wholesale interconnect cost and not a quote**; your real price depends on your own provider and contract.
- **Fail-open.** A slow or failing lookup never blocks your dialer — treat any error as `UNKNOWN` (and `dial_risk` / `cost_estimate` `null`) and proceed under your own lawful basis.
- **Free / bundled.** The pre-call check is not metered.

---

## 3. Enrol your own number for call-provenance

Enrol once per number you own; thereafter every pre-call check from that number records a provenance edge.

```http
POST /api/v1/precall/enroll
X-API-Key: <your key>
Content-Type: application/json

{
  "number": "+14155550100",          # one of YOUR verified business numbers
  "enrolled": true                   # false to revoke
}
```

Response:

```json
{
  "ok": true,
  "number": "+14155550100",
  "enrolled": true,
  "attestation": "By enrolling, you attest that this number only places calls after a Numbers Online outbound pre-call lookup. We treat the absence of a matching pre-call edge as a supplementary signal that a complaint may concern a spoofed call, not one you placed. Enrollment is revocable and abuse-monitored. Supplementary signal only — not a compliance determination."
}
```

Requirements:

- **Account-level key only.** A tenant sub-key cannot enrol (`403`).
- **Your own verified number only.** The number must be OTP-verified and bound to your account (`404` otherwise). Verify a business number first via `POST /api/v1/account/verify-phone/start` → `/confirm`.
- **Revocable.** Send `"enrolled": false` to stop recording edges.

---

## 4. How the shield works (and its limits)

- **Base-rate firewall.** Only enrolled numbers record edges. A non-enrolled number gets the scrub but no provenance inference — so "no edge" is only meaningful for numbers you deliberately enrolled.
- **7-day window.** Edges live for 7 days, which is also the report-matching window. Hashes are byte-identical on both ends, so a report about a call you placed within the window matches your edge.
- **Report-time labels.** A community report about an enrolled number is labelled `matched` (a pre-call edge confirms you dialled this receiver), `mismatched` (enrolled owner + known receiver but no edge → the complaint may concern a spoofed call → withheld from your read and feeds a bounded spoofing signal), or `null` (not applicable).
- **Always hedged.** The shield only fires when there's an enrolled owner, a known receiver, and no matching edge. We never claim certainty — only that a reported call is unlikely to have been yours. It is a supplementary signal, never a block or a verdict, and it is corroboration-gated and abuse-monitored.

---

## 5. Privacy

Both ends of every edge are stored only as SHA-256 hashes of the canonical E.164; raw numbers are never logged or stored. Edges are server-only and expire after 7 days. The provenance label on a report is a supplementary signal — not proof — and is never surfaced to either party as a verdict.

## Keys & references

Keys are self-service at https://numbers.online/docs — shown once, stored only as a hash.
Machine-readable schema: https://numbers.online/api/spec · all guides: https://numbers.online/md/docs/integrations

- Previous guide: [Kamailio & OpenSIPS SIP redirect call screening](https://numbers.online/md/docs/integrations/sbc-sip-redirect)
- Next guide: [FCC robocall-mitigation evidence bundle](https://numbers.online/md/docs/integrations/fcc-evidence-bundle)

---

Numbers Online — community-powered phone-number trust signals. Every figure on this
page is a supplementary, low-confidence signal — advisory, not a verdict on any number
or its owner.

- Canonical page (HTML): https://numbers.online/docs/integrations/precall-call-provenance
- Report a call: https://numbers.online/report · Listed in error? Request removal: https://numbers.online/opt-out
- Machine guide to the phone-intelligence API: https://numbers.online/llms.txt · OpenAPI: https://numbers.online/api/spec
