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

# Org settings orientation: read, patch a scope, read the merge

> Hand-curated walk-through of the org-settings surface — read the current object, patch one scope, and read back the merged result, with the roles note and the 422 validation envelope to copy.

## Read → patch → read back

Org configuration lives on one object per organization (the same record
`GET /api/v1/settings/general` and `GET /api/v1/settings/organization`
expose). Scopes — `voice`, `sms`, `compliance`, `voice-clones`, and the
other slices the dashboard groups under Settings — are keys on that
object, not separate resources. Every change follows one loop: read the
current object, patch the scope you're changing, then read again to see
the merged result. Re-reading before you write is the safest way to learn
what's already set — sibling keys in the same scope are preserved
verbatim, so you never have to resend them.

Reads are available to any member. Writes are role-gated: most are
owner/admin (`PUT /api/v1/settings/general`), and the stricter ones —
consent, marketing, inbox-AI policy, and fraud caps (`PATCH`/`PUT
/api/v1/settings/compliance/*`) — accept the owner role only. Call a
write without the role and you get the standard 403 envelope.

Configuration is server-side only here — no message is sent, no call is
placed, and no routing changes because a scope changed. An outbound
velocity or spend cap constrains your own org's send path only; it never
creates or reroutes an outbound path for another tenant.

### 1. Read the current object

<Note>
  `GET /api/v1/settings/compliance/fraud-caps`
</Note>

Reads return the scope's full current state — your org's overrides plus
the platform floor each override sits on. An override that was never set
comes back `null` (the floor applies), so the response always carries the
complete shape.

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "channels": [
        {
          "channel": "sms",
          "max_per_min": 60,
          "max_daily_spend_cents": 20000,
          "floor_per_min": 20,
          "floor_daily_spend_cents": 5000
        },
        {
          "channel": "whatsapp",
          "max_per_min": null,
          "max_daily_spend_cents": null,
          "floor_per_min": 40,
          "floor_daily_spend_cents": 15000
        }
      ],
      "voice": {
        "max_calls_per_min": null,
        "max_daily_spend_cents": null,
        "country_max_per_min": {},
        "floor_calls_per_min": 30,
        "floor_daily_spend_cents": 10000
      }
    },
    "meta": {
      "request_id": "req_caps_read",
      "timestamp": "2026-08-28T09:12:00.000Z"
    }
  }
  ```
</ResponseExample>

Only `sms` has overrides; `whatsapp` and `voice` ride the platform floor.

### 2. Patch one scope

<Note>
  `PUT /api/v1/settings/compliance/fraud-caps`
</Note>

Send only the scope you're changing. The write is a merge, not a
replace — the `sms` override from step 1 and every untouched scope
survive. A leaf value sets the override; a leaf `null` clears it back to
the platform floor. The response tells you exactly which keys were
written (`set`) and which were removed (`cleared`).

**Request**

```json theme={null}
{
  "caps": {
    "whatsapp": {
      "max_per_min": 100,
      "max_daily_spend_cents": 40000
    }
  }
}
```

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "set": {
        "whatsapp": {
          "max_per_min": 100,
          "max_daily_spend_cents": 40000
        }
      },
      "cleared": []
    },
    "meta": {
      "request_id": "req_caps_patch",
      "timestamp": "2026-08-28T09:12:10.000Z"
    }
  }
  ```
</ResponseExample>

### 3. Read again — the merged result

<Note>
  `GET /api/v1/settings/compliance/fraud-caps`
</Note>

The second read shows the scope you patched next to the overrides you
never touched. `sms` still holds step 1's values; `voice` is still at
the floor. Nothing else moved.

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "channels": [
        {
          "channel": "sms",
          "max_per_min": 60,
          "max_daily_spend_cents": 20000,
          "floor_per_min": 20,
          "floor_daily_spend_cents": 5000
        },
        {
          "channel": "whatsapp",
          "max_per_min": 100,
          "max_daily_spend_cents": 40000,
          "floor_per_min": 40,
          "floor_daily_spend_cents": 15000
        }
      ],
      "voice": {
        "max_calls_per_min": null,
        "max_daily_spend_cents": null,
        "country_max_per_min": {},
        "floor_calls_per_min": 30,
        "floor_daily_spend_cents": 10000
      }
    },
    "meta": {
      "request_id": "req_caps_read_back",
      "timestamp": "2026-08-28T09:12:20.000Z"
    }
  }
  ```
</ResponseExample>

## Validation error envelope

Values outside the documented constraints fail validation — usually a cap
below the platform floor or above the documented maximum — and return
`422` with a `VALIDATION_ERROR` envelope before anything is written:

```json 422 theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "status": 422,
    "message": "caps.whatsapp.max_per_min must be ≥ the platform floor of 40"
  },
  "meta": {
    "request_id": "req_caps_patch_422",
    "timestamp": "2026-08-28T09:12:11.000Z"
  }
}
```

Nothing was stored — the write is accepted or rejected as a whole, so a
scope you offend on never half-applies. Fix the offending value and retry
the same request; read back to confirm the merge.
