> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Composite trust & fraud risk check before a send

> Query POST /risk/score — one 0-100 composite verdict fusing the SMS-pumping, URL-reputation, Verify Fraud Guard, and Voice Biometrics detectors — as a pre-send or pre-launch gate.

# Composite trust & fraud risk check before a send

Orbit ships four independent fraud detectors, each answering its own slice of "is this destination about to cost me money": SMS-pumping (artificial traffic) scoring, outbound URL reputation, Verify Fraud Guard on the OTP path, and Voice Biometrics anti-spoofing. Until now you had to query each one separately and reconcile four differently-banded numbers by hand. `POST /api/v1/risk/score` fuses them into one 0–100 composite verdict you can query *before* committing an SMS send, a campaign launch, or a call.

Everything on this page is **advisory and read-only**. Scoring never sends, routes, blocks, or modifies an outbound message — the Devotel softswitch remains the sole outbound path for every channel. The platform computes the verdict; the decision to act on it is yours.

## 1. Four siloed detectors, one composite

```mermaid theme={null}
flowchart TB
  subgraph detectors[Four siloed detectors]
    SP["SMS-pumping score<br/>(POST /messages/risk-signals)"]
    URL["URL reputation<br/>(compose-time link linter)"]
    VF["Verify Fraud Guard<br/>(fraud score on POST /verify/send)"]
    VB["Voice Biometrics<br/>(anti-spoof challenge)"]
  end
  SP --> C["POST /risk/score<br/>one composite 0-100 verdict"]
  URL --> C
  VF --> C
  VB --> C
```

Each detector grew up behind its own surface with its own request and response shape:

| Detector           | Its own surface                                          | What it scores                                                                                           |
| ------------------ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| SMS-pumping / AIT  | `POST /messages/risk-signals`                            | Destination-prefix patterns, send velocity, delivery conversion, 24h geo-spread, outbound blocklist hits |
| URL reputation     | used inline by the compose-time linter                   | Every link in an outbound body, flagged on phishing / malware / shortened-URL findings                   |
| Verify Fraud Guard | applied on `POST /verify/send` under a configured policy | SIM-swap, geo, and number-intelligence signals on the OTP send path                                      |
| Voice Biometrics   | returned inside an enroll/verify challenge               | Spoof / deepfake probability on a captured audio sample                                                  |

The composite exists so a single pre-send check can see the whole picture. Two of the four components are always computed live for you — the SMS-pumping score and (when you supply a message body) the URL-reputation scan — because both are free, local lookups with no paid upstream call. The other two are accepted as pass-through scores (next section), because triggering them on your behalf would spend money or demand input you have not provided.

## 2. Request shape

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/risk/score" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": "+14155552671",
    "channel": "sms",
    "message_body": "Your order shipped — track it at https://acme.example/t/98421",
    "verify_signal": { "score": 12, "reasons": ["low_sim_swap_risk"] },
    "voice_biometrics_signal": { "score": 4, "reasons": [] }
  }'
```

| Field                     | Required           | Meaning                                                                                                                                                                                                 |
| ------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `destination`             | yes                | E.164 destination you are about to send to or call.                                                                                                                                                     |
| `channel`                 | no (default `sms`) | One of `sms`, `whatsapp`, `voice`, `verify`. Drives which velocity aggregate the SMS-pumping leg reads.                                                                                                 |
| `message_body`            | no                 | The outbound body. When supplied, every URL in it is reputation-scanned and folded into the composite. Omit it and the URL leg simply reports `present: false`.                                         |
| `verify_signal`           | no                 | A `{ score, reasons }` pair (score 0–100) you already hold from a Verify Fraud Guard evaluation for this recipient this session — for example the `riskScore` on a recent `POST /verify/send` response. |
| `voice_biometrics_signal` | no                 | A `{ score, reasons }` pair (score 0–100) you already hold from a Voice Biometrics challenge for this session.                                                                                          |

**Why the two pass-through fields exist instead of the endpoint querying those detectors itself:** a Verify Fraud Guard evaluation spends a paid number-intelligence lookup, and a Voice Biometrics score requires a captured audio sample. Neither may be triggered silently on your behalf from a scoring endpoint. If you already ran either check this session, fold its score in here and get one composite decision; if you have not run one, omit the field — a missing channel contributes nothing rather than inventing risk.

The endpoint authenticates with the `messages:read` or `messages:write` scope and is rate-limited to 120 requests per minute per tenant. A malformed body returns a 422 naming the offending field.

## 3. Interpreting the composite

Every response returns the same three top-level fields, plus a per-channel breakdown:

* **`score`** — the composite risk score, 0 (clean) to 100 (certain block). It is the **maximum of the present channels** — the worst signal wins. A message flagged on any single channel surfaces instead of being averaged back to `low`, and a destination with no signal at all returns a clean 0 rather than a misleading average.
* **`band`** — the score bucketed: below 25 `low`, 25–49 `elevated`, 50–79 `high`, 80+ `critical`. These are the same cut-offs the standalone SMS-pumping scorer uses, so a band means the same thing on `POST /messages/risk-signals` and here.
* **`recommendation`** — the mapped advisory action: `allow` for `low` / `elevated`, `review` for `high`, `block` for `critical`.
* **`channels`** — one entry per detector (`sms_pumping`, `url_reputation`, `verify_fraud`, `voice_biometrics`) with `present`, that channel's own `score`, and its machine-readable `reasons`. The composite is never a black box: the entry driving it is the one with `present: true` and the highest score.

A flagged response looks like:

```json theme={null}
{
  "data": {
    "destination": "+14155552671",
    "channel": "sms",
    "score": 85,
    "band": "critical",
    "recommendation": "block",
    "channels": [
      { "channel": "sms_pumping", "present": true, "score": 85, "reasons": ["high_risk_prefix"] },
      { "channel": "url_reputation", "present": true, "score": 0, "reasons": [] },
      { "channel": "verify_fraud", "present": true, "score": 12, "reasons": ["low_sim_swap_risk"] },
      { "channel": "voice_biometrics", "present": false, "score": 0, "reasons": [] }
    ]
  },
  "meta": { "request_id": "req_9f4c2e" }
}
```

Here the SMS-pumping leg drove the verdict; the Verify pass-through scored clean; Voice Biometrics was never supplied, so it reports `present: false` and contributes nothing.

## 4. Gate pattern — score the audience before a campaign dry-run

Use the composite **before** a launch, not after a bill. The natural gate combines two read-only calls: the launch dry-run tells you the audience and cost picture; the composite tells you whether the destinations in that audience are worth sending to.

Worked pre-launch gate:

1. **Dry-run the campaign** — `POST /api/v1/campaigns/{id}/dry-run` returns the resolved audience size, the deliverable / suppressed / opted-out breakdown, projected cost against the wallet, and a `ready_to_launch` verdict. It never mutates state and never touches the wallet.
2. **Score a sample of the audience** — call `POST /risk/score` for each sampled destination with the campaign's channel and message body.
3. **Decide** — launch only if the dry-run reports `ready_to_launch: true` *and* no sampled destination lands in your block threshold.

```typescript Node.js — pre-launch gate theme={null}
const dryRun = await orbit.request('POST', `/api/v1/campaigns/${campaignId}/dry-run`, {});

if (!dryRun.data.ready_to_launch) {
  throw new Error('Dry-run not clean — fix audience or cost issues first');
}

for (const destination of sampledDestinations) {
  const verdict = await orbit.request('POST', '/api/v1/risk/score', {
    destination,
    channel: 'sms',
    messageBody: campaignBody,
  });
  if (verdict.data.recommendation === 'block') {
    throw new Error(`${destination} scores ${verdict.data.score} — suppress before launch`);
  }
}

// both gates passed — safe to launch
```

The same shape applies to a single high-cost send: score first, branch on `recommendation`, and only then dispatch. `allow` — proceed; `review` — queue for a human when the send is high-value; `block` — suppress the destination and log the decision. Enforcement always stays in your code; the endpoint only advises.

## 5. Channel examples

The `channel` value selects which velocity aggregate the SMS-pumping leg reads — set it to the channel you are actually about to use.

**SMS** (the default):

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/risk/score" \
  -H "X-API-Key: $ORBIT_API_KEY" -H "Content-Type: application/json" \
  -d '{"destination": "+14155552671", "channel": "sms",
       "message_body": "Your code is 384216. Reply STOP to opt out."}'
```

**WhatsApp** — velocity reads the WhatsApp lane instead of the SMS lane:

```json theme={null}
{ "destination": "+14155552671", "channel": "whatsapp" }
```

**Voice** — score the outbound call target before a dialer or agent call; fold in a Voice Biometrics challenge score if you ran one this session:

```json theme={null}
{
  "destination": "+14155552671",
  "channel": "voice",
  "voice_biometrics_signal": { "score": 71, "reasons": ["spoof_suspected"] }
}
```

**Verify** — score the OTP destination and pass through the Fraud Guard evaluation you already received on a recent `POST /verify/send`:

```json theme={null}
{
  "destination": "+14155552671",
  "channel": "verify",
  "verify_signal": { "score": 88, "reasons": ["sim_swap_recent"] }
}
```

## 6. Limits and caveats

* **Read-only and advisory.** The endpoint never dispatches, routes, or blocks anything. Outbound SMS and voice exit only through the Devotel softswitch; a `block` recommendation suppresses nothing until your own send code acts on it.
* **Tenant-owned decision.** Thresholds, sampling, and what `review` means operationally are your configuration. If you want the platform to enforce a terminal allow / review / block decision, configure **Fraud Shield** on the messaging side or **Fraud Guard** on the Verify side — those are separate tenant-owned controls; this endpoint only scores.
* **No pass-through re-querying.** `verify_signal` and `voice_biometrics_signal` are never re-evaluated server-side — supply scores you already hold from this session, or omit them.
* **Absent channel, no invented risk.** A channel you did not supply (or a message body you did not send) reports `present: false` and counts as zero. An all-absent input returns `score: 0, band: "low"`, not an error.
* **Score scope.** The composite reflects the detectors listed above. A clean verdict is not a deliverability guarantee — it says nothing flagged on the fraud axes the platform measures.

Canonical pages: [Risk API](/api-reference/endpoints/risk), [SMS-pumping protection](/guides/sms-pumping-protection), [Fraud Shield configuration](/guides/compliance-fraud-shield), [Voice biometrics enrollment](/guides/voice-biometrics-enrollment-walkthrough), [API recipes](/guides/api-recipes).
