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

# API recipes: operations endpoints

> Runnable curl loops for the operational surfaces — drive the Verify second-officer approvals round trip end to end, arm a per-message cascade_policy through route-preview before smart-send, query the message-history search DSL over the API, and set the SMPP no-delivery receipt window on a termination rule.

# API recipes: operations endpoints

Four operational surfaces endpoint-guides cover conceptually but only sample once. Each recipe below is a runnable loop — read, write, decide — over the exact endpoints the linked page documents. Base URL is `https://api.orbit.devotel.io/api/v1` throughout; authenticate with `X-API-Key` and a test key (`dv_test_sk_…`) for the sandbox.

## 1. Verify approvals: run the second-officer round trip

The four-eyes gate on [Verify approvals](/guides/verify-approvals-two-officer) has a full queue lifecycle — policy, queue, decision — and a client needs all three calls to drive it.

**Read the current policy** to predict whether your next change will queue (any authenticated member can read it):

```bash cURL theme={null}
curl https://api.orbit.devotel.io/api/v1/verify/approvals/settings \
  -H "X-API-Key: dv_test_sk_YOUR_KEY"
```

**Turn the gate on** (owner/admin only — the requester's own key is usually not this):

```bash cURL theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/verify/approvals/settings \
  -H "X-API-Key: dv_test_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "require_approval_default": true,
    "resend_burst_threshold": 25,
    "resend_burst_window_seconds": 300
  }'
```

From the next request onward, an OTP template edit or an over-threshold resend burst returns `gated: true` and queues instead of applying. **Read the queue** the approver's key sees:

```bash cURL theme={null}
curl https://api.orbit.devotel.io/api/v1/verify/approvals/pending \
  -H "X-API-Key: dv_test_sk_YOUR_KEY"
```

**Decide one item** — approve, or reject with a reason the requester reads back. The approver must differ from the requester: a self-approve fails `409 FOUR_EYES_VIOLATION` before anything moves.

```bash cURL theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/verify/approvals/vap_9f2a1c/approve \
  -H "X-API-Key: dv_test_sk_YOUR_KEY"
```

```bash cURL — reject instead theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/verify/approvals/vap_9f2a1c/reject \
  -H "X-API-Key: dv_test_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Tighten the wording and re-submit." }'
```

An approved item executes in its own plane — a template body applies to its verification profile; a resend burst re-enters the normal dispatch path. Expected results are `200` with `data.settings` on reads, `200` with the decided item on approve/reject, and `403` on any of the three writes from a non-owner/admin key. Every decision lands in the [audit log](/guides/audit-log) as `verify.approval.approved` / `verify.approval.rejected`.

Canonical pages: [Verify approvals: set up the second-officer gate](/guides/verify-approvals-two-officer), [OTP approvals reference](/verify/otp-approvals).

## 2. Fallback chains: preview the resolved cascade, then let smart-send arm it

A per-message `cascade_policy` resolves an ordered fallback set without sending when you run it through `POST /messages/route-preview`. Preview before you arm — the [Fallback chains](/guides/fallback-chains) guide's step 7 names both preview endpoints; this is the API loop it refers to.

**Preview the router's primary plus the cascade** — same body shape as smart-send, read-only:

```bash cURL theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/messages/route-preview \
  -H "X-API-Key: dv_test_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+14155552671",
    "body": "Your order 98421 shipped.",
    "message_type": "transactional",
    "urgency": "medium",
    "country": "US",
    "cascade_policy": {
      "fallback_channels": ["sms"],
      "auto_fallback": true
    }
  }'
```

The reply carries the recommendation block the [smart route preview guide](/guides/smart-route-preview) documents, plus a resolved `cascade` block when `cascade_policy` was in the request: the fallback channel list, the `ordering` (`static` / `adaptive` / `off`), and a cost estimate. A `200` with `data.cascade.ordering: "off"` means the policy suppressed the arm — the send below would ride the primary channel with no fallback stamped.

**Arm it with the real send** — the same `cascade_policy` on `POST /messages/smart-send` stamps the resolved chain onto the message's metadata as `fallback_channels`, so a terminal DLR on the primary escalates to the next entry:

```bash cURL theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/messages/smart-send \
  -H "X-API-Key: dv_test_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+14155552671",
    "body": "Your order 98421 shipped.",
    "message_type": "transactional",
    "urgency": "medium",
    "cascade_policy": {
      "fallback_channels": ["sms"],
      "auto_fallback": true
    }
  }'
```

`202 Accepted` returns the persisted send with `data.channel` naming the router's primary pick and the fallback set stamped on its metadata. `voice` and `fax` are filtered out of `cascade_policy.fallback_channels` — the per-message cascade is messaging-only; OTP step-up chains that admit `voice` belong on a [Verify profile](/guides/verify-fallback-chains), per the chain-type table in [Fallback chains](/guides/fallback-chains#1-choose-the-chain-type-per-use-case).

Canonical pages: [Smart-send fallback chains](/guides/smart-send-fallback-chains), [Smart route preview](/guides/smart-route-preview).

## 3. Message history: run the `q=` search DSL over the API

The [search message history guide](/guides/search-message-history) documents the fielded DSL; the API loop adds the list endpoint, URL-encoding rules, and cursor pagination. Encode the expression — spaces to `%20`, `:` to `%3A`, `>` to `%3E` — and it rides `GET /messages` alongside the pagination params:

```bash cURL theme={null}
curl "https://api.orbit.devotel.io/api/v1/messages?q=channel%3Asms%20status%3Afailed%20created%3E2026-08-20&limit=25" \
  -H "X-API-Key: dv_test_sk_YOUR_KEY"
```

Page the matches with the same cursor contract as any list — `meta.pagination.cursor` while `has_more` is true:

```bash cURL theme={null}
curl "https://api.orbit.devotel.io/api/v1/messages?q=channel%3Asms%20status%3Afailed%20created%3E2026-08-20&limit=25&cursor=eyJpZCI6InJtc2dfMDFIWFYvLi4uIn0=" \
  -H "X-API-Key: dv_test_sk_YOUR_KEY"
```

A malformed expression returns `422 INVALID_QUERY` naming the rejected field, operator, or clause (`INVALID_QUERY_FIELD` on an unknown field, `INVALID_QUERY_SYNTAX` on an unterminated quote) — fix the named clause; never retry the same string. Each matching row carries both the provider-side identifier (`external_id`) and the canonical Orbit id (`id`, `msg_…`), so a look-up can deep-link into the message detail page.

Canonical pages: [Search message history](/guides/search-message-history), [Pagination](/guides/pagination).

## 4. SMPP: set the no-delivery receipt window on a termination rule

The [SMPP receipt timeout window](/guides/smpp-receipt-timeout-window) guide sets `receipt_timeout_seconds` on a termination rule's `deliver` hop (10–120 s, platform default 30). This loop hashes the exact API body the guided contract ships and where it goes.

**Write the rule** — absorb `submit_sm` traffic onto WhatsApp with a 45-second UNDELIV window:

```bash cURL theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/messaging/termination/rules \
  -H "X-API-Key: dv_live_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "OTP via WhatsApp — 45s fallback window",
    "priority": 10,
    "enabled": true,
    "mode": "enforce",
    "direction": "mt_absorb",
    "ingress_kind": "smpp_submit",
    "match": {
      "all": [
        { "field": "dest_addr", "op": "prefix", "value": "+91" }
      ]
    },
    "actions": [
      {
        "kind": "deliver",
        "channel": "whatsapp",
        "template_name": "verify_user",
        "template_language": "en",
        "on_failure": "next",
        "receipt_timeout_seconds": 45
      },
      { "kind": "absorb" }
    ]
  }'
```

`201 Created` returns the rule. Out-of-range or non-integer windows (`25.5`, `0`, `121`) answer `422` at rule-write time, and a rule in `shadow` mode evaluates without moving traffic until you flip it to `enforce`. The deadline only arms for submissions whose `registered_delivery` requested a receipt: a bind that never requests receipts gets none from the timeout either.

Canonical pages: [SMPP receipt timeout window](/guides/smpp-receipt-timeout-window), [Send-side DLR model](/concepts/send-side-dlr-model).

## See also

* [REST API recipes](/guides/api-recipes) — the main task-by-task cookbook these four loops extend
* [How to read a worked sample](/guides/using-orbit-samples) — translate any curl block here into your HTTP client
* [API error handling by example](/guides/error-handling-examples) — branch on `error.code` for every failure shape above
* [Verify fallback chains and advanced factors](/guides/verify-fallback-chains) — the OTP step-up chain a per-message cascade does not cover
