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

# Assemble your STIR/SHAKEN attestation posture

> Tenant-owned walkthrough for building a defensible STIR/SHAKEN posture end to end: ownership-first level resolution, delegate-certificate lifecycle for BYON numbers, the per-DID inbound floor, branded calling with CNAM fallback, reporting policy, and the posture snapshot.

# Assemble your STIR/SHAKEN attestation posture

This guide is the walkthrough form of the
[STIR/SHAKEN attestation posture concept page](/compliance/attestation): it
assembles the posture in the order the resolver applies it, with worked curl
calls at every step. The hard problem — landing at attestation level A on
your outbound numbers — is not one toggle. It is six small surfaces working
in the right order. Run the steps top-down; steps 1–4 raise the level your
traffic signs at, steps 5–6 make the resulting posture visible, and the
troubleshooting hooks at the end close the loop when reality doesn't match
the posture you chose.

<Warning>
  Attestation posture is **tenant-owned**. You choose the target level, the
  downgrade handling, the inbound floor, the delegate-certificate coverage,
  and the branded-calling configuration. Orbit records and enforces what you
  set; it does not mandate a posture, and the carrier of record never signs
  higher than the platform attests. Regulatory judgment about which level a
  given calling programme must hold stays with you and your counsel. Nothing
  on this page is legal advice, and no step gates, blocks, reroutes, or
  re-signs a call.
</Warning>

## How the parts fit

Before the steps, the model in one picture:

| Layer | What it decides | Where you set it |
| - | - | - |
| Ownership resolver | The A/B/C level a call signs at | Step 1 — buy, port, or lease numbers |
| Delegate certificates | C → B on BYON / external numbers | Step 2 — the certificate registry |
| Inbound floor | Which inbound signatures count as verified, and per-DID rejection floor | Step 3 — policy + per-DID route |
| Branded calling (RCD) | Brand name, logo, reason-for-call rendered on A-attested calls | Step 4 — brand settings |
| Reporting policy | The target and handling the posture snapshot measures against | Step 5 — attestation policy |
| Posture snapshot | The read-only measurement of all of it | Step 6 — posture endpoint |

Signing itself never happens on Orbit. The platform signals the resolved
level on the outbound INVITE; Devotel's wholesale softswitch — the carrier
of record — signs the PASSporT at exactly the level the platform signals.
For the full signing model, see
[STIR/SHAKEN attestation](/channels/voice/stir-shaken).

***

## Step 1 — Resolve ownership first (the A/B/C ladder)

The dial-time resolver walks a fixed ownership order, before any policy is
consulted:

1. **Owned number → A (full).** A caller ID that is an active number your
   organization owns through Orbit — purchased on-platform or ported in and
   billed to your org — attests at A. This is the only path to full
   attestation.
2. **Leased pool number → B (partial).** A pool number with an active
   assignment to your org attests at B. Orbit authenticates your org, but
   pool ownership is not your verified right-to-use.
3. **Anything else → C (gateway).** A verified external caller ID, a hosted
   or BYON number you control but do not own in Orbit, or an unattributable
   caller ID attests at C — the weakest signal and the one downstream
   carriers most often label "Spam Likely."

When a caller ID matches more than one arm, the stronger attribution wins —
a number the org owns outright attests A even if it also rides an active
pool lease.

Read `{ "source": "owned" | "leased" }` off the posture snapshot next to the
level calls from that number actually attest at — the snapshot is the truth
source, not your intuition about what the org holds.

**No step on this page substitutes for Step 1.** If answer rates matter,
steps 2–4 refine the posture for numbers the org already owns or
legitimately controls; they never leapfrog a non-owned number to A.

***

## Step 2 — Run the delegate-certificate lifecycle (BYON: C → B, never A)

A **delegate certificate** (ATIS-1000092) is the artifact your service
provider hands you to authorize specific numbers you control but do not own
in Orbit. Registering it with Orbit raises the covered numbers from C to
**B — partial, never full**.

The ceiling is deliberate: the certificate is a tenant-supplied artifact,
Orbit does not cryptographically validate its chain, and the covered-number
list you record is free-form input not bound to the certificate's real
TNAuthList — so B ("customer authenticated, number authorization not
provider-verified") is the correct rating for that evidence. A
self-registered artifact must never be able to spoof full attestation.

### Register a certificate

Obtain the PEM chain from your provider, then register it with its coverage
list and validity window:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/compliance/attestation/delegate-certs" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "friendly_name": "BYON caller-ID coverage",
    "certificate_pem": "-----BEGIN CERTIFICATE-----\n…\n-----END CERTIFICATE-----",
    "covered_numbers": ["+14155581234", "+14155580987"],
    "covered_ranges": [{ "start": "+14155585500", "end": "+14155585599" }],
    "not_before": "2026-10-01T00:00:00Z",
    "not_after": "2027-10-01T00:00:00Z"
  }'
```

At least one covered number or range is required (it is the TNAuthList you
record). Numbers are E.164; ranges are inclusive on both ends with
`start <= end`. Write-time failures are precise:

* **`422 INVALID_CERTIFICATE`** — no parseable PEM certificate block in
  `certificate_pem` (wrong encoding, truncated chain, or a non-certificate
  payload).
* **`422 INVALID_COVERAGE_RANGE`** — a range had `start > end`.
* **`422 VALIDATION_ERROR`** — a malformed field (non-E.164 number, invalid
  timestamp).
* **`409 DELEGATE_CERT_DUPLICATE`** — the same chain fingerprint is already
  registered; the numbers already attest at its level.
* **`409 DELEGATE_CERT_LIMIT`** — the 50-certificates-per-org cap is full;
  revoke an unused certificate first.

### Watch the effective status, not the submission time

The registry derives each certificate's **effective status live**:

| Effective status | What it means for coverage |
| - | - |
| `active` | Covered numbers attest at B now |
| `pending` | `not_before` is still ahead — coverage has not begun |
| `expired` | `not_after` has passed — coverage ended |
| `revoked` | You explicitly revoked the record — coverage ended |

A certificate that flips to `pending`, `expired`, or `revoked` stops
covering its numbers, and those numbers fall back to their ownership-based
level at the next call — with no call blocked and no error emitted. Read
the registry with:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/compliance/attestation/delegate-certs" \
  -H "Authorization: Bearer $ORBIT_API_KEY"
```

### Renew before the window closes

Re-register a renewed chain **before** the current certificate's
`not_after` passes — the drop happens at the flip, not after it. Revoke a
certificate you no longer control with:

```bash theme={null}
curl -X DELETE "https://api.orbit.devotel.io/api/v1/compliance/attestation/delegate-certs/$CERT_ID" \
  -H "Authorization: Bearer $ORBIT_API_KEY"
```

Revocation is immediate and cannot be undone; the record is kept with a
revoked-by line for audit. Do not re-register a revoked certificate's chain
— obtain and register a fresh one. Certificate writes are owner/admin
gated; certificate reads are not.

### Retirement is ownership

A delegate certificate is the gap-fill, not the destination. The permanent
fix for a covered number is porting it into Orbit (Step 1): once ownership
settles, the coverage is redundant and the certificate can be revoked.

***

## Step 3 — Set the inbound verification floor (per-DID policy)

Inbound calls arrive with STIR signalling (a pre-verified `Verstat` result,
or a raw `Identity` header), and Orbit parses it. Two floors apply:

* **Org-level — `inbound_min_verification`** (Step 5) decides which inbound
  signature levels count as *verified* in your posture snapshot. Reporting
  only; it never rejects a call.
* **Per-DID minimum attestation** on the inbound route is the hard floor:
  when a route's minimum attestation is set, inbound calls whose parsed
  level falls below it are declined with SIP **603** before routing.

Manage the per-DID route with the inbound-routing surface:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/numbers/+14155580100/routing" \
  -H "Authorization: Bearer $ORBIT_API_KEY"
```

The floor levels are A, B, or C. Because the resolver treats **unsigned**
inbound (`unknown`) as below any letter floor, setting the per-DID floor
rejects unsigned callers as well as ones that simply failed signing — leave
the floor unset (the default) to admit any signed call regardless of level,
or set it only on DIDs where you deliberately want the stricter posture. A
rejected inbound call carries a readable **603 Decline** reason and
rejection is observable per call, before any media path opens.

<Note>
  The per-DID floor applies to **inbound** only. It never affects outbound
  attestation, outbound routing, or outbound signing — those resolve in
  Steps 1–2 and the policy in Step 5.
</Note>

***

## Step 4 — Wire branded calling (RCD) with a CNAM fallback per DID

**Rich Call Data (RCD)** is the STIR/SHAKEN extension that renders your
verified brand on the recipient's handset — display name, logo, and
reason-for-call — on **A-attested calls only**. Delegates at B, and
unattributed C, never carry it. RCD is the posture's payoff surface: the
handset treatment that actually lifts answer rate on owned numbers.

### Org-level configuration

Set the org default (owner/admin) — the same surface as **Settings →
Voice → Branded calling**:

```bash theme={null}
curl -X PUT "https://api.orbit.devotel.io/api/v1/settings/branded-calling" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "display_name": "Acme Support",
    "logo_url": "https://assets.example.com/logo.png",
    "reason_for_call": "Appointment confirmation"
  }'
```

Validation at write time:

* `display_name` is 1–40 characters (carriers truncate longer at the wire).
* `reason_for_call` is up to 60 characters (the strictest downstream cap).
* `logo_url` must be a public, TLS-served, stable PNG/JPEG/WebP URL — no
  authentication headers, no `http://`, no internal hosts. An out-of-spec
  URL is a `422`, not a stored-and-later-failing config.
* `holdout_pct` (optional, 0–50) withholds RCD from that fraction of
  otherwise-branded calls to measure answer-rate lift honestly — a
  randomized control, not a before/after. Omitting it leaves a running
  experiment untouched.

A save that changes any rendered asset (name, logo, reason) resets the
brand-verification state to unverified — the revised brand re-enters
review before it renders again. Toggling `enabled` or adjusting
`holdout_pct` alone does not reset the verdict.

### Per-DID override and the CNAM fallback

The org-level brand paints every outbound number; a **per-number profile**
on the DID overrides the org default for that one number, so a multi-brand
fleet can carry a caller's brand only on their numbers. The override
precedence:

* **Profile enabled and fully specified** (name + logo) → the profile
  assets render.
* **Profile disabled or incomplete** (missing name or logo) → the org
  default renders, unchanged.
* **Carrier not registered** → RCD drops silently for that call; the
  recipient sees plain attestation **plus any CNAM name on file**. That
  CNAM record is the fallback label, not a second brand.

Register the per-DID calling name with CNAM so the fallback is never bare:

```bash theme={null}
curl -X PUT "https://api.orbit.devotel.io/api/v1/numbers/+14155580100/cnam" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "display_name": "Acme Support" }'
```

See [CNAM & caller ID](/numbers/cnam) for the LIDB/dispatch lifecycle. The
fallback hierarchy therefore reads:

* **RCD renders** — verified brand on the handset.
* **RCD not carried (non-A attestation, unregistered carrier, incomplete
  profile)** — plain attestation result, plus the CNAM name if one is
  registered.
* **No CNAM either** — the bare number, and the spam-likely treatment
  downstream carriers hand generic traffic.

For the full RCD surface — the holdout experiment, the per-carrier
registration list, and the failure modes — see
[Branded calling](/compliance/branded-calling).

***

## Step 5 — Set the reporting policy (target level, downgrade handling, reporting floor)

The org-level **attestation policy** declares what you intend (target
level), what to do with below-target traffic (downgrade handling), the
inbound signature floor treated as verified for fraud posture, and whether
failed/unsigned inbound calls count as spoof-risk. It never gates a call —
it drives the posture snapshot's measurement.

```bash theme={null}
curl -X PUT "https://api.orbit.devotel.io/api/v1/compliance/attestation/policy" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "target_attestation": "A",
    "downgrade_handling": "alert",
    "inbound_min_verification": "B",
    "flag_unverified_inbound": true
  }'
```

| Field | Values | Default | What it measures |
| - | - | - | - |
| `target_attestation` | `A`, `B`, `C` | `A` | The level you intend your outbound numbers to reach; below-target numbers are flagged |
| `downgrade_handling` | `monitor`, `alert` | `monitor` | How below-target traffic is shown in reporting — observability only |
| `inbound_min_verification` | `any`, `A`, `B`, `C` | `B` | The inbound signing level treated as verified for posture |
| `flag_unverified_inbound` | `true`, `false` | `true` | When on, failed/unsigned inbound counts as spoof-risk rather than merely unverified |

Writes are **partial** — a write that names only `target_attestation`
leaves the inbound floor and the spoof-risk flag untouched. Reads
(`GET /compliance/attestation/policy`) always return 200 with a complete
policy object; an org that has never set one gets the safe defaults. The
intent field here is the one a tenant sets **on the dashboard surface
(Settings → Compliance → Attestation)** or over this endpoint — always the
same store either way. Invalid values are a `422` and every successful
write lands in your audit log. Owner/admin can write; any member can read.

<Note>
  The policy is **reporting intent, not a gate**. Setting a target while
  your traffic runs on leased pool numbers (B) does not raise or lower what
  gets signed — the softswitch signs at exactly the level the platform
  attests, and your posture snapshot simply starts flagging those numbers
  as below target. Do not PUT a higher target hoping the level follows; the
  level follows ownership (Steps 1–2).
</Note>

***

## Step 6 — Read the posture snapshot

`GET /compliance/attestation/posture` is the read-only measurement you
assemble everything against. It answers the two questions this guide is
built around at once:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/compliance/attestation/posture" \
  -H "Authorization: Bearer $ORBIT_API_KEY"
```

```json theme={null}
{
  "data": {
    "policy": { "target_attestation": "A", "downgrade_handling": "alert",
                "inbound_min_verification": "B", "flag_unverified_inbound": true,
                "updated_at": "2026-08-20T14:03:11.000Z" },
    "originating": {
      "summary": { "total": 12, "meeting_target": 9, "below_target": 3,
                   "by_level": { "A": 9, "B": 3, "C": 0 } },
      "numbers": [
        { "phone_number": "+14155580100", "source": "owned", "attestation": "A", "meets_target": true },
        { "phone_number": "+14155580777", "source": "leased", "attestation": "B", "meets_target": false }
      ],
      "numbers_truncated": false
    },
    "inbound": {
      "window_days": 30,
      "window_start": "2026-07-27T10:15:00.000Z",
      "window_end": "2026-08-26T10:15:00.000Z",
      "summary": { "total": 1840, "verified": 1712, "unverified": 96,
                   "spoof_risk": 32, "verified_rate": 0.9304 }
    }
  }
}
```

* **`originating.numbers[]`** lists each originating number with the level
  calls from it actually attest at (`owned` → A, `leased` → B), and
  `meets_target` compares that to your Step-5 target. The list caps at 500
  numbers (`numbers_truncated` tells you when you hit the cap) so a sender
  with thousands of DIDs sees totals first and specific held-back
  identifiers next — not an aggregate hiding the offender.
* **`inbound.summary`** classifies the last 30 days of inbound calls to
  DIDs your org currently owns: signed at-or-above the floor → `verified`;
  signed-below-floor or off-net → `unverified`; failed or unsigned →
  `spoof_risk` when the flag is on, else `unverified`. `verified_rate` is
  the verified share of all inbound calls, or `null` (not zero) when the
  window had no inbound traffic.

Both halves are org-scoped: originating rows come from your own numbers and
assignments only, and inbound counts calls that arrived on a DID your org
currently owns — a released DID reassigned to a new owner starts that
owner's inbound history clean. Reads fail soft: if an underlying lookup is
unavailable, the affected half returns empty or zeros rather than failing
the whole snapshot.

Recheck the snapshot after any Step-1-to-5 change — the posture posture
confirms the change the way a diff confirms a patch, and an unreconciled
`snapshot` is exactly the case Step 7 works.

***

## Troubleshooting hooks

When the snapshot or the recipient-side treatment disagrees with what you
declared, two guided pages pick up from here rather than re-explaining the
build:

* [STIR/SHAKEN attestation downgrade](/troubleshooting/stir-shaken-attestation-downgrade)
  — the systematic root-cause run for a call that signs lower than your
  target: the C ceiling on non-owned numbers, an expired or mis-registered
  delegate certificate, a per-number branded override, and carrier
  coverage gaps in RCD — plus the fix run for each and the error-code map
  (`INVALID_CERTIFICATE`, `INVALID_COVERAGE_RANGE`, `DELEGATE_CERT_LIMIT`,
  `DELEGATE_CERT_DUPLICATE`).
* [STIR/SHAKEN never measured](/troubleshooting/stir-shaken-not-measured) —
  the quick-start for a tenant who has never opened the surface: read the
  current policy defaults, put the inbound reporting fields back to the
  expected defaults (`inbound_min_verification: "B"`, the flag on), and
  run the first posture measurement.

## See also

* [STIR/SHAKEN attestation](/channels/voice/stir-shaken) — what the A/B/C
  levels mean, where Orbit signals the level, and where the Devotel
  softswitch signs it.
* [Attestation posture (concept)](/compliance/attestation) — the single
  concept page this walkthrough runs.
* [Branded calling](/compliance/branded-calling) — the RCD surface the
  payoff step wires.
* [CNAM & caller ID](/numbers/cnam) — the fallback label the branded-
  calling step pairs with.
* [The STIR/SHAKEN attestation model](/concepts/stir-shaken-attestation-model) —
  PASSporT flow, delegated attestation, and where the gate surfaces.
