> ## 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.

# Fraud caps

> Size the anti-abuse velocity and daily-spend limits on each messaging channel and on voice: read back the effective caps, raise or lower a channel below the platform floor, and use the per-country voice map to squeeze expensive destinations.

# Fraud caps

Fraud caps are your account's anti-abuse limits. Each outbound send
runs against two thresholds: a per-minute velocity cap (how fast) and a
daily-spend cap in cents (how much). When a cap trips, the send is
rejected — which is exactly what you want when something on your side,
or an attacker on theirs, starts generating anomalous traffic.

The platform sets **floors** — conservative defaults per channel — that
apply even with no override configured. This page shows how to read
the floors, tighten a channel below its floor as a wallet guard, and
size a cap for a real workload.

The endpoint is
`GET` / `PUT /api/v1/settings/compliance/fraud-caps`. Reads are open to
any authenticated member of your organization; writes are restricted
to owners. Full request shapes are in the
[settings API reference](/api-reference/settings).

***

## What a fraud cap is — and is not

A fraud cap is an **abuse control, not a throughput control.** Too-low
per-minute limits generate `429` rejections; too-high limits let a leak
or a toll-fraud attack run up spend before anyone notices. Two
different knobs answer those questions on this platform — don't
conflate them:

| Control                    | What it caps                                                                              | Failure posture                   |
| -------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------- |
| **Fraud caps** (this page) | Per-minute send velocity and daily spend, per channel and for voice                       | Falls back to the platform floor  |
| **Channel rate overrides** | Throughput envelope — how fast the API accepts your legitimate traffic on dedicated sends | Falls back to the cluster default |

Raise your envelope with
[Channel rate overrides](/compliance/channel-rate-overrides) only after
sizing the fraud guard here. A send must pass both gates.

***

## Reading floors and overrides

`GET /api/v1/settings/compliance/fraud-caps` returns one object with
two blocks:

* **`channels`** — one entry per covered channel (`sms`, `mms`,
  `whatsapp`, `rcs`, `viber`, `email`, `telegram`, `push`, `web_chat`,
  `messenger`, `line`, `instagram`, `fax`, `apple_messages`). Each
  entry carries `max_per_min` and `max_daily_spend_cents` — your
  override, or `null` when the floor applies — plus `floor_per_min` and
  `floor_daily_spend_cents` from the platform.
* **`voice`** — the envelope for outbound calls:
  `max_calls_per_min`, `max_daily_spend_cents`, a per-destination-country
  `country_max_per_min` map (see below), and the voice floors.

Read the object back before every change — `max_per_min: null` on a
channel means "floor applies," not "unlimited."

If the settings lookup fails transiently, the response degrades to
floors-only (all overrides `null`) rather than a `5xx`. The dashboard
panel still renders, and enforcement keeps using the floors — a read
error never widens your exposure.

***

## Writing overrides

`PUT /api/v1/settings/compliance/fraud-caps` is **owner-only**. The
body carries two optional blocks, and both are **sparse**: only
channels you include are touched — everything else keeps its stored
value. Inside a channel entry, a `null` leaf clears just that override
and reverts to the floor.

```bash theme={null}
curl -X PUT "https://api.orbit.devotel.io/api/v1/settings/compliance/fraud-caps" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "caps": {
      "sms": {
        "max_per_min": 20,
        "max_daily_spend_cents": 50000
      }
    },
    "voice": {
      "max_calls_per_min": 30,
      "country_max_per_min": { "US": 10 }
    }
  }'
```

Because an unset override always falls back to the floor, clearing a
cap (send `null`) is safe — it widens back to the platform guard, never
to "no guard." To restore a whole channel's defaults, clear both
leaves and omit nothing else:

```bash theme={null}
curl -X PUT "https://api.orbit.devotel.io/api/v1/settings/compliance/fraud-caps" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"caps": {"sms": {"max_per_min": null, "max_daily_spend_cents": null}}}'
```

Values are integers within a sanity ceiling the send path also
enforces, so a value accepted here can never be silently rejected at
dispatch time. A `422` on the write means a leaf was non-integer,
non-positive, or above that ceiling — fix the payload and retry.

The `voice.country_max_per_min` map is replaced wholesale on every PUT
that includes it: send the entire map you want active. An empty map
clears all per-country caps.

<Note>
  `voice.max_calls_per_min` caps the overall outbound dial rate, while
  `voice.country_max_per_min` clamps one named ISO country
  independently. On voice, `0` is a legal value: a country's mid-attack
  "stop dialing this destination" lever that doesn't exist on the
  messaging channels.
</Note>

***

## How voice fraud interacts with auto-blocks

Per-country caps are one of three voice-side layers you should
understand together:

1. **Platform floors** — always-on per-tenant and per-country call-rate
   guards, plus a curated premium-rate prefix blocklist.
2. **Your overrides** — the `voice` block above: overall velocity,
   daily spend, and per-country rates you tighten below the floors.
3. **Automatic per-destination blocks** — when a detector sweep sees
   traffic-pumping (IRSF revenue-share) or a Wangiri callback burst
   concentrated on one specific number, it applies a reversible,
   self-expiring block on exactly that destination. Legitimate calls to
   every other number are unaffected.

Sustained attacks often express as a per-country spike before any
single number trips a block: if your account dials into an expensive
destination at a rate you never approved, cap that country here (or
set it to `0`) while the anomaly is investigated. Full coverage of the
auto-block categories, lifetimes, and read endpoints is in
[Voice destination auto-blocks](/compliance/voice-destination-auto-blocks).

***

## Worked sizing examples

### US toll-free SMS, a marketing dispatcher

A mid-size sender runs a campaign that delivers about 1,200 SMS per
minute on peak days, with a verified sender registration. The account
has never had a fraud incident, but an operator wants damage-bound
limits in case a queue duplicates jobs or a token leaks.

* **Velocity.** `1,200 / 60 = 20` sends per second. Leave headroom for
  bursts: set `sms.max_per_min` to the sustained peak — `1200` — not
  the average.
* **Spend.** At $0.0075 per segment and ~1.2 segments per message,
  peak hour spend is `1200 × 60 × 1.2 × 0.0075 ≈ $648/hr`. A daily
  budget of roughly 8 peak hours is `500000`cents ($5,000). Set`sms.max\_daily\_spend\_cents\` there — the first anomalous spike that
  runs past a working day cuts itself off.
* **Verify.** `PUT` the map, then `GET` the endpoint: `sms` shows your
  values in `max_per_min` and `max_daily_spend_cents`, and the floors
  appear alongside (so you can see the delta you introduced). Run a
  batch against the cap — at 20+ msg/sec you'll see the floor of
  rejections stop exactly at the limit you chose, while a second run
  under the cap completes cleanly.

### Voice IVR campaign, outbound customer callbacks

A utility dials opted-in customers for payment reminders from an
IVR-driven dialer: about 25 concurrent legs at any moment, only US
destinations, \~\$40/day in voice spend.

* **Overall rate.** Set `voice.max_calls_per_min` to `30` — a hair
  above the observed 25/minute peaks — so a wedged dialer loop
  rejecting-retrying can't sustain a spike.
* **Country clamp.** Cap `voice.country_max_per_min` on the one
  destination country the campaign touches (`US: 12`) so a misrouted
  campaign to a premium-tier destination is cut at the source.
* **Spend.** Set `voice.max_daily_spend_cents` to `6000` (\$60) — 1.5×
  the normal daily bill, so anomalies still stop but a busy Monday
  doesn't stall.

In both cases, the cap should sit **above your legitimate peak and
below any plausible attack volume** — treat the floor as a backstop,
and your override as the primary budget boundary.

***

## Audit trail and verification

Every `PUT` writes an entry to your audit log naming the capped keys —
the resulting override map and the cleared list — so a raised or
cleared guard is part of the trail, not a silent change. Owners verify
a change in three steps, the same loop as on
[channel-rate-overrides](/compliance/channel-rate-overrides):

1. **Set.** `PUT` the overrides (owner role).
2. **Read back.** `GET` the endpoint and confirm each field carries
   your values; cleared leaves show `null` (floor applies).
3. **Confirm on the send path.** Send on the capped channel at your
   historic rate and watch rejections stop at the new boundary —
   previously the same burst only stopped at the platform floor.

The same control is available under **Settings → Compliance** in the
dashboard: each channel's current override is shown next to the
platform floor, and a voice block carries the per-country map editor.
Owners see the write action; other members see the effective caps.
Reads and writes on this surface are tenant-scoped — you only ever see
and change your own organization's limits.
