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

# Operating Compliance-Health Scores as an Early-Warning Signal

> The operator cadence behind the compliance-health score: how to poll it into your alerting, which band transitions trigger which runbook steps, and how to route each warning to the deep page that remediates it.

# Operating Compliance-Health Scores as an Early-Warning Signal

The [compliance-health score](/compliance/compliance-health) is only
useful if someone watches it, and the dashboard panel is only open when a
human happens to look. This runbook is the operator cadence that turns a
read-only 0–100 score into an early-warning signal: poll the endpoints on
a fixed schedule, alert on band transitions rather than raw numbers, and
route every `warnings[]` entry to the deep page that actually remediates
its factor.

<Note>
  The health surface is **read-only advisory** — nothing below blocks a
  send, suppresses a contact, or gates traffic. Every remediation step
  here is a **tenant-owned control you configure and own**; Orbit provides
  the surfaces and defaults them open. This page is an operational
  runbook, not legal advice — confirm which obligations apply to your
  traffic with qualified counsel.
</Note>

***

## 1. The alert cadence

Poll the three read endpoints hourly and alert on **band transitions**,
not absolute scores. `GET /compliance/health/campaigns` and
`GET /compliance/health/numbers` return worst-first rows; a single
hourly call to each typically surfaces every scope that moved. A once an
hour cadence catches drift while it is still a `watch`-band trend — the
stage where remediation is a targeting fix, not an incident.

A minimal poller (any cron host, CI schedule, or monitoring runner):

```bash theme={null}
#!/usr/bin/env bash
set -euo pipefail
base="https://api.orbit.devotel.io/api/v1/compliance"

check() { # $1 = endpoint, $2 = jsonpath of worst band, $3 = scope label
  band=$(curl -s "$base/$1" -H "Authorization: Bearer $ORBIT_API_KEY" \
    | python3 -c "import sys,json; d=json.load(sys.stdin)['data']; \
print(d['band'])" )
  echo "$(date -u +%FT%TZ) $3 band=$band" 
}

check "health?window_days=30" "" "organization"
check "health/numbers?window_days=30" "" "worst-sender"
check "health/campaigns?window_days=30" "" "worst-campaign"
```

Wire it into your existing alerting tool the same way you would any
synthetic check, or adapt the request-signing pattern from
[API integration](/guides/api-integration). Two rules keep the alert
useful:

* **Alert on the transition, not the value.** Store the previous band per
  scope (a file, a monitoring label, a small KV) and notify only when it
  moves. A score that lives at `watch` for a quarter is background; a
  score that *enters* `watch` is the page.
* **Alert on warning severity, too.** An `at_risk` band with no change is
  routine follow-up; a warning whose `severity` reaches `critical` —
  especially `carrier_rejection` — deserves the runbook below even when
  the band label stubbornly reads the same.

***

## 2. The score-drop runbook

Map each band transition to a fixed response. The point of a runbook is
that the response is decided before the alert fires.

| Transition           | First response                                                                                                                                                                   |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `healthy → watch`    | Log it, read the `warnings[]` entry, and queue the deep-page fix (Section 3) for the owning team.                                                                                |
| `watch → at_risk`    | Run the **consent-coverage check** and a **suppression review**, below.                                                                                                          |
| `at_risk → critical` | Run the **critical-factor checklist** — compliance-profile lifecycle errors — and **rehearse the emergency stop** so the kill switch is not theory if it becomes the right call. |
| `→ unknown`          | Not a score drop — every factor reported `insufficient_data`. Verify the scope actually sent traffic in the window.                                                              |

### `watch → at_risk` — consent coverage + suppression review

1. **Open the organization scope** `GET /compliance/health` and read the
   `consent_coverage` factor value — share of contacted recipients holding
   a current granted consent.
2. **Trace the gap** with the [Consent API](/compliance/consent-management):
   look up a sample of recently-contacted recipients and find the capture
   path (import, form, inbound keyword) that never wrote a consent record.
   Fix the capture path, not the score.
3. **Review suppression** in
   [Opt-Out & Suppression](/compliance/opt-out-suppression): confirm the
   opt-out path is enforced end to end and that recent STOP replies were
   actually suppressed — a broken suppression path shows up as rising
   opt-out and STOP factors before it shows up as a complaint.

### `at_risk → critical` — lifecycle errors + emergency-stop rehearsal

1. **Re-read with a short window** (`window_days=7`) to confirm the drop
   is current drift and not a trailing-window artifact.
2. **Resolve the worst `warnings[]` factor** through its deep page
   (Section 3). Only one factor is usually acute; fix it before touching
   anything else.
3. **If `carrier_rejection` is the acute factor**, work the
   [compliance-profile lifecycle errors](/compliance/compliance-profile-lifecycle-errors)
   checklist: a `422` REQUIRED/NOT\_APPROVED means the sender's profile
   packet never cleared, and a `409` LOCKED/IN\_USE blocks the fix until
   you clone-and-fix. A sender with an unapproved profile draws carrier
   rejections until the packet clears.
4. **Rehearse the [emergency stop](/compliance/emergency-stop)** once —
   an owner/admin activates, verifies the blocked-send `403` code,
   deactivates, and confirms the status read. The
   [emergency-stop runbook](/guides/compliance-emergency-stop) has the
   drill procedure. Rehearsing it in a `critical` band is how the control
   stays tenant-owned *and* operable: an operator who has never flipped
   the switch in peacetime will fumble it in an incident. Trigger it for
   real only when the cost of one more send outweighs halting everything —
   suspected list contamination, an active abuse signal, a carrier
   complaint wave.

***

## 3. Map warnings\[] to deep pages

Each `warnings[]` entry names exactly one factor. Route it to the page
whose control moves that factor — the recommendation text in the entry
usually points at the right one; this table is the fixed mapping.

| `warnings[].factor` | Remediation path                                                                                                                                                                                                         |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `consent_coverage`  | [Consent Management](/compliance/consent-management) — close the capture-path gap in the Consent API flow, not in the score.                                                                                             |
| `opt_out_velocity`  | [Opt-Out & Suppression](/compliance/opt-out-suppression) — verify suppression works, then slow send volume and review targeting/frequency.                                                                               |
| `stop_reply_rate`   | [Opt-Out Keyword Alias Table](/compliance/opt-out-keyword-alias-table) — confirm STOP variants resolve to suppression, then review the opt-out copy and audience fit.                                                    |
| `carrier_rejection` | [Compliance Profile Lifecycle Errors](/compliance/compliance-profile-lifecycle-errors) for registration/approval failures; otherwise check 10DLC/sender registration and filtered message content before scaling volume. |

There is no reset button for any of these — the score recovers when the
underlying metric recovers, and a fresh-window re-read is how you confirm
the fix landed.

***

## 4. Tenant-owned framing

Health scores never block. The whole point of the cadence above is that
**you** decide what a `watch` trend, an `at_risk` band, or a `critical`
warning means for your traffic — Orbit computes the signal and defaults
every gate open. Nothing in this runbook is mandatory, and no step here
modifies your traffic unless you run it. Treat the score the way you
treat a carrier's complaint desk: the early read is only as useful as the
runbook it feeds, and the runbook is yours.

***

## 5. Worked example: one band transition

A spring-promo sender (`+14155550101`) sits at `healthy` for weeks. Your
hourly poller logs:

```
2026-09-21T12:00:00Z worst-sender band=watch
```

1. **Tue 12:00, `healthy → watch`:** the alert fires once. The sender row
   shows a `consent_coverage` warning — a weekend import of 12,000
   recipients dropped coverage from 98% to 81%. The owning team gets a
   ticket; no traffic change.
2. **Tue 15:00, runbook runs:** a sample lookup through the
   [Consent API](/compliance/consent-management) confirms the import path
   never wrote consent records. The team re-permissions the list through
   the import consent flow and verifies records exist for sampled
   recipients.
3. **Wed 00:00, next hourly poll:** `watch` holds — expected, the 30-day
   window still holds the gap.
4. **Next day, band returns to `healthy`:** the import flow now writes
   consent at capture, and a `window_days=7` re-read confirms coverage at
   97%. The poller's single alert was the entire cost — one page,
   one transition, one fix.

Contrast with the un-runbooked path: the same import unpolled reaches
`at_risk` within a week, and the first `critical` carrier-rejection
warning arrives at campaign launch — the moment volume spikes and the
throttle costs the most.

***

## Related references

* [Compliance Health Scores](/compliance/compliance-health) — the
  endpoint contract, factor weights, and band thresholds this runbook
  reads.
* [Compliance Profile Lifecycle Errors](/compliance/compliance-profile-lifecycle-errors) — REQUIRED / NOT\_APPROVED / LOCKED / IN\_USE fixes for
  profile-gated senders.
* [Emergency Stop](/compliance/emergency-stop) — the org-wide outbound
  halt rehearsed at Step 4 of the critical runbook.
* [Emergency Stop Runbook](/guides/compliance-emergency-stop) — the drill
  procedure: activate, verify the 403, deactivate, read status.
* [Posture Overview](/compliance/posture-overview) — the per-control
  enforcement surface map behind your tenant-owned posture.
* [Quarterly Compliance Posture Review](/guides/compliance-posture-quarterly-review) — the
  slower cadence this hourly signal feeds into.
