# Settings

## Get channel settings

**get** `/v1/settings`

Retrieves your brand's channel settings: the editable fields, the link
state, and the managed integration groups as display strings.

**Behavior**

- Nothing here is secret: no client secret, no certificate material.
- `business_hours` and `hours_timezone` are absent when the brand
  publishes no hours. Absence means "no hours model": the brand is
  treated as always open; never a default week.

### Returns

- `BrandSettings object { agent_enabled, app_extensions, brand_id, 16 more }`

  The brand's channel settings: what a brand edits itself, plus the managed groups and the link state, read-only.

  - `agent_enabled: boolean`

    Whether your AI agent is switched on. `true` for a brand that has never
    changed it.

    While this is `false` we stop delivering webhooks to your brand's
    endpoints, so your agent is told nothing and answers nothing. Events that
    occur during the pause are **never** delivered; not held and replayed
    when you switch back on, which is what makes this safe to use during an
    incident: your agent will not wake up and answer an hour of stale
    messages.

    The mute covers your WHOLE webhook feed, not only the events an agent
    answers: `message.sent` / `message.failed`, `consent.expired` and the
    invitation-batch events travel the same fan-out, so delivery outcomes and
    invitation status go dark too and do not backfill. The read side is
    unaffected and is the during-mute channel; `GET /v1/messages/{msg}`,
    `GET /v1/invitations/{inv}` and `GET /v1/chats/{chat}/events` all still
    answer, so nothing is lost from the record, only from the push.

    It does **not** stop you sending. Your API key keeps working while the
    switch is off, which is what lets a human desk carry a conversation your
    agent is muted for.

  - `app_extensions: object { apps }`

    The registered apps that are OPERATIVE for your brand, keyed by the same
    `app_key` space `PUT /v1/settings/integrations/app_extensions` writes:
    your stored registry when you have one, the deploy default otherwise, and
    `managed[name=app_extensions].source` says which.

    **Read this before assembling a write.** The registry's precedence unit is
    the WHOLE set: a write replaces your stored registry outright, and a stored
    registry masks the deploy default entirely. So an app you leave out of a
    write stops resolving on the send path: a `200` and a silently
    unregistered app. This object is what makes a correct whole-set write
    possible: it is the complete operative registry, so send it back with your
    edits applied rather than assembling one from your own records.

    Unlike `apple_pay` and `oauth` above, this key is ALWAYS PRESENT: an
    empty `apps` is how "nothing is registered" is said, and it is safe to
    edit from. A registry is never "unconfigured" the way a merchant can be:
    one is always operative, so there is always an answer. The only way this
    key is absent is a platform build older than the field, and a client that
    cares about the difference should read an absent key as "cannot speak to
    the registry" and refuse to write the group; never as an empty one.

    App icons are never returned, on the same terms as the certificate and the
    client secret above: `app_icon_set` per entry is the whole of it.

    - `apps: unknown`

      The complete operative registry, `app_key` → app. Empty when nothing is
      registered. Every key here is a key `PUT /v1/settings/integrations/app_extensions`
      accepts, and that write replaces this whole map, so edit this object and
      send it back rather than composing one.

  - `brand_id: string`

    The brand this key is scoped to: the same id `GET /v1/me` reports.

  - `business_id: string`

    The linked business id, `""` when no channel is linked. The full row is `GET /v1/channels`.

  - `consent_mode: "linq_managed" or "brand_attested"`

    Who captures consent GRANTS for this brand. `linq_managed` (the
    default): we capture grants, and a grant attested through
    `POST /v1/consent` answers HTTP 409 `code` 2014.
    `brand_attested`: you attest grants yourself through that endpoint.
    Read-only here: the mode is operator-set; contact support to change it.
    Opt-outs are honored in both modes; the mode governs only who may
    record a GRANT.

    - `"linq_managed"`

    - `"brand_attested"`

  - `display: string`

    The brand's display name.

  - `escalation_handling: "managed" or "self_managed"`

    Who runs escalation for this brand: your stored choice, exactly as you
    set it, and `self_managed` until you set one. See `escalation_handling`
    on the update request for what each value means. While
    `escalation_handling_locked` is `true` this stored value is not
    operative: we have forced the brand to `managed` escalation.

    - `"managed"`

    - `"self_managed"`

  - `escalation_handling_locked: boolean`

    `true` while we hold this brand on `managed` escalation regardless of
    its stored choice. It is the state we use when monitoring shows a
    partner-run human path not answering. Updates to `escalation_handling` are
    refused while it is `true`; contact support to release it.

  - `link_origin: string`

    The link's origin (`seed` | `connect` | `control`), `""` when no channel is linked.

  - `managed: array of ManagedSetting`

    The read-only groups, as display strings with a managed-by marker.

    - `managed_by: "linq"`

      Who changes it. Always `linq` today: these are registered with the platform as well as with us, so they change together; write to your support contact.

      - `"linq"`

    - `name: "apple_pay_merchant" or "app_extensions" or "oauth"`

      Which setting: the Apple Pay merchant, the registered iMessage apps, or customer authentication (OAuth).

      - `"apple_pay_merchant"`

      - `"app_extensions"`

      - `"oauth"`

    - `set: boolean`

      Whether the group is operative for a send, evaluated on the RESOLVED
      view: your brand's stored configuration when present, the deploy
      default otherwise. `false` means a send needing this group is refused
      (`409`) until it is configured.

    - `source: "brand" or "platform"`

      Which copy is operative: `brand` when your brand's stored configuration answers, `platform` when the deploy default does.

      - `"brand"`

      - `"platform"`

    - `value: string`

      A display string; `merchant.com.example`, `2 registered`, `configured`,
      `not configured`. Never key material, secrets or certificate references;
      this field is safe to render verbatim.

  - `policy_version: number`

    Monotonic policy version. Bumping it (see the update's `bump_policy_version`) is what re-notifies customers of a policy change, so it only ever moves explicitly.

  - `privacy_policy_url: string`

    Absolute `https` URL of the privacy policy, `""` when unset.

  - `richlink_default_image_att_id: string`

    The brand's rich-link default card image (`att_…`), `""` when the platform default stands.

  - `support_contact: string`

    The brand's support contact, `""` when unset.

  - `terms_of_use_url: string`

    Absolute `https` URL of the terms of use, `""` when unset.

  - `apple_pay: optional object { display_name, domain, merchant_id, merchant_identity_cert_set }`

    The brand's Apple Pay configuration, non-secret fields only. ABSENT when
    the group is not configured; never an object of empty strings, the same
    absent-means-unconfigured promise `business_hours` makes.

    The merchant-identity certificate is NEVER returned, here or anywhere:
    it goes up write-only and lives in the secret store. `merchant_identity_cert_set`
    is the whole of what this contract says about it, so a client can offer
    "leave blank to keep the stored certificate" without learning anything.

    - `display_name: string`

      The business name Apple renders on the payment sheet.

    - `domain: string`

      The verified payment domain the merchant session is requested for.

    - `merchant_id: string`

      The Apple Pay merchant identifier (`merchant.example.brand`).

    - `merchant_identity_cert_set: boolean`

      Whether a merchant-identity certificate is on file. The certificate
      itself is never returned; omit `merchant_identity_cert_pem` on a write to
      keep the stored one.

  - `business_hours: optional unknown`

    Business hours as stored: a LIST of `["HH:MM","HH:MM"]` ranges per
    lowercase weekday key (`mon`…`sun`), so a lunch break or a split shift is
    expressible.

    ABSENT: the key is not present at all; when the brand publishes no
    hours, never `{}` and never an invented 9-5. That is the same promise
    `GET /v1/chats/{chat}`'s `brand` makes about the same document: an
    unconfigured brand has no hours model and the platform treats it as always
    open, which is a different fact from "open 24/7 by policy" and is not
    spelled the same way.

  - `hours_timezone: optional string`

    The IANA zone the hours are stated in (`America/New_York`). Absent when
    unset. Hours are never configured without it; the two keys are
    independently optional here only because a direct operator edit can leave
    a zone behind with no hours.

  - `oauth: optional object { authorize_url, client_id, client_secret_set, 4 more }`

    The brand's OAuth configuration, non-secret fields only. ABSENT when the
    group is not configured. The client secret is never returned, on the same
    terms as the certificate above.

    - `authorize_url: string`

      Where the customer is sent to authorize.

    - `client_id: string`

      The OAuth client id.

    - `client_secret_set: boolean`

      Whether a client secret is on file. The secret itself is never returned;
      omit `client_secret` on a write to keep the stored one.

    - `issuer_url: string`

      The identity provider's issuer URL.

    - `redirect_uri: string`

      The redirect the provider returns to.

    - `scope: string`

      The space-separated scopes requested.

    - `token_url: string`

      Where the authorization code is exchanged.

### Example

```http
curl https://messages.api.linqapp.com/v1/settings \
    -H "Authorization: Bearer $LINQ_AMB_API_KEY"
```

#### Response

```json
{
  "brand_id": "biz_9f2c1a",
  "display": "CurbFare",
  "privacy_policy_url": "https://example.com/privacy",
  "terms_of_use_url": "https://example.com/terms",
  "policy_version": 3,
  "support_contact": "support@example.com",
  "richlink_default_image_att_id": "",
  "agent_enabled": true,
  "escalation_handling": "self_managed",
  "escalation_handling_locked": false,
  "consent_mode": "linq_managed",
  "business_hours": {
    "mon": [
      [
        "09:00",
        "12:00"
      ],
      [
        "13:00",
        "17:00"
      ]
    ],
    "sat": [
      [
        "10:00",
        "14:00"
      ]
    ]
  },
  "hours_timezone": "America/New_York",
  "business_id": "a884eddf-b0ad-4be4-9c0e-071531638768",
  "link_origin": "connect",
  "managed": [
    {
      "name": "apple_pay_merchant",
      "value": "not configured",
      "managed_by": "linq",
      "set": false,
      "source": "platform"
    },
    {
      "name": "app_extensions",
      "value": "none registered",
      "managed_by": "linq",
      "set": false,
      "source": "platform"
    },
    {
      "name": "oauth",
      "value": "configured",
      "managed_by": "linq",
      "set": true,
      "source": "brand"
    }
  ],
  "app_extensions": {
    "apps": {}
  }
}
```

## Update channel settings

**put** `/v1/settings`

Updates your brand's editable settings.

**Behavior**

- An absent field keeps its current value; send only what you mean to
  change.
- `bump_policy_version` is the one explicit act: a version bump
  re-notifies customers and never happens as a side effect.
- Business hours are one setting spelled in two keys: send either
  `business_hours` or `hours_timezone` and the other falls back to what
  is stored; the merged pair is validated whole and written whole, so a
  refusal leaves the stored week untouched. Clear both together with
  `"business_hours": {}` and `"hours_timezone": ""`.
- Returns the updated view, exactly as `GET /v1/settings` would.
- `escalation_handling` is the optional opt-in for having us run
  escalation (`managed`); every brand starts as `self_managed`. The
  field's own documentation carries the full contract and what the
  `chat.trigger_word` event looks like in each mode.

**Errors**

- HTTP 422 `code` 1021: `escalation_handling` is
  neither `managed` nor `self_managed`.
- HTTP 409 `code` 2019: we hold your brand on `managed`
  escalation and the update named a differing value; re-sending the
  stored value still succeeds as a no-op.
- HTTP 413 `code` 1073: the hours document exceeds 4,096 bytes.
- HTTP 422: `business_hours_invalid` (wrong shape: an unknown weekday
  key, a range that is not `["HH:MM","HH:MM"]`, a time outside
  `00:00`–`24:00`), `business_hours_end_not_after_start`,
  `business_hours_overlap`, `business_hours_never_open`,
  `hours_timezone_invalid`, and `required` when hours arrive without a
  zone or a zone without hours.

### Body Parameters

- `agent_enabled: optional boolean`

  Switch your AI agent on or off; see `agent_enabled` on the response for
  what the pause does and does not cover. Absent leaves it as it is; there
  is no other way to spell "unchanged" for a boolean, so a settings update
  that omits this can never switch your agent off by accident.

- `bump_policy_version: optional boolean`

  `true` increments `policy_version`: the explicit act that re-notifies
  customers of a policy change. NEVER implicit: editing a URL alone does not
  bump, because an accidental bump re-messages every customer.

- `business_hours: optional unknown`

  Your opening times: a LIST of `["HH:MM","HH:MM"]` ranges per lowercase
  weekday key. Absent keeps the stored document; `{}` together with
  `hours_timezone: ""` CLEARS the hours, which is how "we do not publish
  hours" is spelled.

  `business_hours` and `hours_timezone` are ONE SETTING: send either and the
  other falls back to what is stored, and the merged pair is validated
  whole. Hours without a zone are refused (`422 required` on
  `/hours_timezone`) and a zone without hours is refused the same way on
  `/business_hours`, so clearing means sending BOTH empty, not one.

  THE WRITER IS STRICTER THAN THE READER, in three ways worth knowing before
  you compose a week (each has its own code, listed on the operation):
  `end` must come AFTER `start`: a range that reads back as crossing
  midnight is refused on write, and the message names the split-shift
  spelling to use instead; two ranges on one weekday may not share a minute
  (they may touch); and the week must open somewhere.

- `escalation_handling: optional "managed" or "self_managed"`

  Who runs escalation for this brand. OPTIONAL and an OPT-IN: every brand
  starts as `self_managed`, absent leaves your stored choice unchanged, and
  re-sending your stored value is a no-op, never an error. To opt into our
  desk, `PUT /v1/settings` with `{"escalation_handling": "managed"}`; the
  response is the full settings view with the new value. To take
  escalation back, send `"self_managed"` the same way.

  `self_managed` (the default): escalation is yours. We detect the
  request for a person and deliver the `chat.trigger_word` event with
  `handled_by: "partner"` and NO delegation fields: no `queued_for_human`,
  no `ack_by`. Nothing is queued, the chat's owner does not move, and your
  agent keeps the floor; your ordinary sends keep answering exactly as on
  any partner-owned chat, and none of them meet `409 chat_owned`, because
  the ownership that refusal guards never moved. The ABSENCE of
  `queued_for_human` on that event is how your webhook handler knows this
  escalation is yours; from there you decide whether to answer in place,
  hand the chat to your own human desk and tell us with `/handoff`, or
  call `/clarify` to opt that turn back into our escalation. The way to a
  person is your escalation experience, the one your Experience Review
  approved, and the live-human obligation in that review is yours. The
  event's own documentation shows a worked payload for each shape.

  `managed` (the opt-in): when a customer asks for a person, we queue
  them for a human at that moment and you enrich the acknowledgement,
  by `ack_by`.

  We may force a brand onto `managed` if monitoring shows its human path
  is not answering; while forced, a differing value is refused
  `409 escalation_handling_locked` and the response shows
  `escalation_handling_locked: true`. A value other than the two above is
  refused `422 escalation_handling_invalid`.

  - `"managed"`

  - `"self_managed"`

- `hours_timezone: optional string`

  The IANA zone your hours are stated in (`America/New_York`). Absent keeps
  the stored zone; `""` alongside an empty `business_hours` clears the pair.
  `Local` is refused (`422 hours_timezone_invalid`); it resolves to the
  serving container's zone, not yours.

- `privacy_policy_url: optional string`

  Absolute `https` URL, at most 2048 characters, or `""` to unset. `http` is refused (`400 invalid_url`): these URLs are rendered to customers.

- `richlink_default_image_att_id: optional string`

  An `att_…` id your brand uploaded (a PNG ≤ 200 KB), or `""` to clear back to the platform default. Validated at set time, same rules as a rich link's image.

- `support_contact: optional string`

  Free text, trimmed; stored to 320 characters.

- `terms_of_use_url: optional string`

  Same rules as `privacy_policy_url`.

### Returns

- `BrandSettings object { agent_enabled, app_extensions, brand_id, 16 more }`

  The brand's channel settings: what a brand edits itself, plus the managed groups and the link state, read-only.

  - `agent_enabled: boolean`

    Whether your AI agent is switched on. `true` for a brand that has never
    changed it.

    While this is `false` we stop delivering webhooks to your brand's
    endpoints, so your agent is told nothing and answers nothing. Events that
    occur during the pause are **never** delivered; not held and replayed
    when you switch back on, which is what makes this safe to use during an
    incident: your agent will not wake up and answer an hour of stale
    messages.

    The mute covers your WHOLE webhook feed, not only the events an agent
    answers: `message.sent` / `message.failed`, `consent.expired` and the
    invitation-batch events travel the same fan-out, so delivery outcomes and
    invitation status go dark too and do not backfill. The read side is
    unaffected and is the during-mute channel; `GET /v1/messages/{msg}`,
    `GET /v1/invitations/{inv}` and `GET /v1/chats/{chat}/events` all still
    answer, so nothing is lost from the record, only from the push.

    It does **not** stop you sending. Your API key keeps working while the
    switch is off, which is what lets a human desk carry a conversation your
    agent is muted for.

  - `app_extensions: object { apps }`

    The registered apps that are OPERATIVE for your brand, keyed by the same
    `app_key` space `PUT /v1/settings/integrations/app_extensions` writes:
    your stored registry when you have one, the deploy default otherwise, and
    `managed[name=app_extensions].source` says which.

    **Read this before assembling a write.** The registry's precedence unit is
    the WHOLE set: a write replaces your stored registry outright, and a stored
    registry masks the deploy default entirely. So an app you leave out of a
    write stops resolving on the send path: a `200` and a silently
    unregistered app. This object is what makes a correct whole-set write
    possible: it is the complete operative registry, so send it back with your
    edits applied rather than assembling one from your own records.

    Unlike `apple_pay` and `oauth` above, this key is ALWAYS PRESENT: an
    empty `apps` is how "nothing is registered" is said, and it is safe to
    edit from. A registry is never "unconfigured" the way a merchant can be:
    one is always operative, so there is always an answer. The only way this
    key is absent is a platform build older than the field, and a client that
    cares about the difference should read an absent key as "cannot speak to
    the registry" and refuse to write the group; never as an empty one.

    App icons are never returned, on the same terms as the certificate and the
    client secret above: `app_icon_set` per entry is the whole of it.

    - `apps: unknown`

      The complete operative registry, `app_key` → app. Empty when nothing is
      registered. Every key here is a key `PUT /v1/settings/integrations/app_extensions`
      accepts, and that write replaces this whole map, so edit this object and
      send it back rather than composing one.

  - `brand_id: string`

    The brand this key is scoped to: the same id `GET /v1/me` reports.

  - `business_id: string`

    The linked business id, `""` when no channel is linked. The full row is `GET /v1/channels`.

  - `consent_mode: "linq_managed" or "brand_attested"`

    Who captures consent GRANTS for this brand. `linq_managed` (the
    default): we capture grants, and a grant attested through
    `POST /v1/consent` answers HTTP 409 `code` 2014.
    `brand_attested`: you attest grants yourself through that endpoint.
    Read-only here: the mode is operator-set; contact support to change it.
    Opt-outs are honored in both modes; the mode governs only who may
    record a GRANT.

    - `"linq_managed"`

    - `"brand_attested"`

  - `display: string`

    The brand's display name.

  - `escalation_handling: "managed" or "self_managed"`

    Who runs escalation for this brand: your stored choice, exactly as you
    set it, and `self_managed` until you set one. See `escalation_handling`
    on the update request for what each value means. While
    `escalation_handling_locked` is `true` this stored value is not
    operative: we have forced the brand to `managed` escalation.

    - `"managed"`

    - `"self_managed"`

  - `escalation_handling_locked: boolean`

    `true` while we hold this brand on `managed` escalation regardless of
    its stored choice. It is the state we use when monitoring shows a
    partner-run human path not answering. Updates to `escalation_handling` are
    refused while it is `true`; contact support to release it.

  - `link_origin: string`

    The link's origin (`seed` | `connect` | `control`), `""` when no channel is linked.

  - `managed: array of ManagedSetting`

    The read-only groups, as display strings with a managed-by marker.

    - `managed_by: "linq"`

      Who changes it. Always `linq` today: these are registered with the platform as well as with us, so they change together; write to your support contact.

      - `"linq"`

    - `name: "apple_pay_merchant" or "app_extensions" or "oauth"`

      Which setting: the Apple Pay merchant, the registered iMessage apps, or customer authentication (OAuth).

      - `"apple_pay_merchant"`

      - `"app_extensions"`

      - `"oauth"`

    - `set: boolean`

      Whether the group is operative for a send, evaluated on the RESOLVED
      view: your brand's stored configuration when present, the deploy
      default otherwise. `false` means a send needing this group is refused
      (`409`) until it is configured.

    - `source: "brand" or "platform"`

      Which copy is operative: `brand` when your brand's stored configuration answers, `platform` when the deploy default does.

      - `"brand"`

      - `"platform"`

    - `value: string`

      A display string; `merchant.com.example`, `2 registered`, `configured`,
      `not configured`. Never key material, secrets or certificate references;
      this field is safe to render verbatim.

  - `policy_version: number`

    Monotonic policy version. Bumping it (see the update's `bump_policy_version`) is what re-notifies customers of a policy change, so it only ever moves explicitly.

  - `privacy_policy_url: string`

    Absolute `https` URL of the privacy policy, `""` when unset.

  - `richlink_default_image_att_id: string`

    The brand's rich-link default card image (`att_…`), `""` when the platform default stands.

  - `support_contact: string`

    The brand's support contact, `""` when unset.

  - `terms_of_use_url: string`

    Absolute `https` URL of the terms of use, `""` when unset.

  - `apple_pay: optional object { display_name, domain, merchant_id, merchant_identity_cert_set }`

    The brand's Apple Pay configuration, non-secret fields only. ABSENT when
    the group is not configured; never an object of empty strings, the same
    absent-means-unconfigured promise `business_hours` makes.

    The merchant-identity certificate is NEVER returned, here or anywhere:
    it goes up write-only and lives in the secret store. `merchant_identity_cert_set`
    is the whole of what this contract says about it, so a client can offer
    "leave blank to keep the stored certificate" without learning anything.

    - `display_name: string`

      The business name Apple renders on the payment sheet.

    - `domain: string`

      The verified payment domain the merchant session is requested for.

    - `merchant_id: string`

      The Apple Pay merchant identifier (`merchant.example.brand`).

    - `merchant_identity_cert_set: boolean`

      Whether a merchant-identity certificate is on file. The certificate
      itself is never returned; omit `merchant_identity_cert_pem` on a write to
      keep the stored one.

  - `business_hours: optional unknown`

    Business hours as stored: a LIST of `["HH:MM","HH:MM"]` ranges per
    lowercase weekday key (`mon`…`sun`), so a lunch break or a split shift is
    expressible.

    ABSENT: the key is not present at all; when the brand publishes no
    hours, never `{}` and never an invented 9-5. That is the same promise
    `GET /v1/chats/{chat}`'s `brand` makes about the same document: an
    unconfigured brand has no hours model and the platform treats it as always
    open, which is a different fact from "open 24/7 by policy" and is not
    spelled the same way.

  - `hours_timezone: optional string`

    The IANA zone the hours are stated in (`America/New_York`). Absent when
    unset. Hours are never configured without it; the two keys are
    independently optional here only because a direct operator edit can leave
    a zone behind with no hours.

  - `oauth: optional object { authorize_url, client_id, client_secret_set, 4 more }`

    The brand's OAuth configuration, non-secret fields only. ABSENT when the
    group is not configured. The client secret is never returned, on the same
    terms as the certificate above.

    - `authorize_url: string`

      Where the customer is sent to authorize.

    - `client_id: string`

      The OAuth client id.

    - `client_secret_set: boolean`

      Whether a client secret is on file. The secret itself is never returned;
      omit `client_secret` on a write to keep the stored one.

    - `issuer_url: string`

      The identity provider's issuer URL.

    - `redirect_uri: string`

      The redirect the provider returns to.

    - `scope: string`

      The space-separated scopes requested.

    - `token_url: string`

      Where the authorization code is exchanged.

### Example

```http
curl https://messages.api.linqapp.com/v1/settings \
    -X PUT \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $LINQ_AMB_API_KEY" \
    -d '{}'
```

#### Response

```json
{
  "brand_id": "biz_9f2c1a",
  "display": "CurbFare",
  "privacy_policy_url": "https://example.com/privacy",
  "terms_of_use_url": "https://example.com/terms",
  "policy_version": 3,
  "support_contact": "support@example.com",
  "richlink_default_image_att_id": "",
  "agent_enabled": true,
  "escalation_handling": "managed",
  "escalation_handling_locked": false,
  "consent_mode": "linq_managed",
  "business_hours": {
    "mon": [
      [
        "09:00",
        "12:00"
      ],
      [
        "13:00",
        "17:00"
      ]
    ],
    "sat": [
      [
        "10:00",
        "14:00"
      ]
    ]
  },
  "hours_timezone": "America/New_York",
  "business_id": "a884eddf-b0ad-4be4-9c0e-071531638768",
  "link_origin": "connect",
  "managed": [
    {
      "name": "apple_pay_merchant",
      "value": "not configured",
      "managed_by": "linq",
      "set": false,
      "source": "platform"
    },
    {
      "name": "app_extensions",
      "value": "none registered",
      "managed_by": "linq",
      "set": false,
      "source": "platform"
    },
    {
      "name": "oauth",
      "value": "configured",
      "managed_by": "linq",
      "set": true,
      "source": "brand"
    }
  ],
  "app_extensions": {
    "apps": {}
  }
}
```

## Domain Types

### Brand Settings

- `BrandSettings object { agent_enabled, app_extensions, brand_id, 16 more }`

  The brand's channel settings: what a brand edits itself, plus the managed groups and the link state, read-only.

  - `agent_enabled: boolean`

    Whether your AI agent is switched on. `true` for a brand that has never
    changed it.

    While this is `false` we stop delivering webhooks to your brand's
    endpoints, so your agent is told nothing and answers nothing. Events that
    occur during the pause are **never** delivered; not held and replayed
    when you switch back on, which is what makes this safe to use during an
    incident: your agent will not wake up and answer an hour of stale
    messages.

    The mute covers your WHOLE webhook feed, not only the events an agent
    answers: `message.sent` / `message.failed`, `consent.expired` and the
    invitation-batch events travel the same fan-out, so delivery outcomes and
    invitation status go dark too and do not backfill. The read side is
    unaffected and is the during-mute channel; `GET /v1/messages/{msg}`,
    `GET /v1/invitations/{inv}` and `GET /v1/chats/{chat}/events` all still
    answer, so nothing is lost from the record, only from the push.

    It does **not** stop you sending. Your API key keeps working while the
    switch is off, which is what lets a human desk carry a conversation your
    agent is muted for.

  - `app_extensions: object { apps }`

    The registered apps that are OPERATIVE for your brand, keyed by the same
    `app_key` space `PUT /v1/settings/integrations/app_extensions` writes:
    your stored registry when you have one, the deploy default otherwise, and
    `managed[name=app_extensions].source` says which.

    **Read this before assembling a write.** The registry's precedence unit is
    the WHOLE set: a write replaces your stored registry outright, and a stored
    registry masks the deploy default entirely. So an app you leave out of a
    write stops resolving on the send path: a `200` and a silently
    unregistered app. This object is what makes a correct whole-set write
    possible: it is the complete operative registry, so send it back with your
    edits applied rather than assembling one from your own records.

    Unlike `apple_pay` and `oauth` above, this key is ALWAYS PRESENT: an
    empty `apps` is how "nothing is registered" is said, and it is safe to
    edit from. A registry is never "unconfigured" the way a merchant can be:
    one is always operative, so there is always an answer. The only way this
    key is absent is a platform build older than the field, and a client that
    cares about the difference should read an absent key as "cannot speak to
    the registry" and refuse to write the group; never as an empty one.

    App icons are never returned, on the same terms as the certificate and the
    client secret above: `app_icon_set` per entry is the whole of it.

    - `apps: unknown`

      The complete operative registry, `app_key` → app. Empty when nothing is
      registered. Every key here is a key `PUT /v1/settings/integrations/app_extensions`
      accepts, and that write replaces this whole map, so edit this object and
      send it back rather than composing one.

  - `brand_id: string`

    The brand this key is scoped to: the same id `GET /v1/me` reports.

  - `business_id: string`

    The linked business id, `""` when no channel is linked. The full row is `GET /v1/channels`.

  - `consent_mode: "linq_managed" or "brand_attested"`

    Who captures consent GRANTS for this brand. `linq_managed` (the
    default): we capture grants, and a grant attested through
    `POST /v1/consent` answers HTTP 409 `code` 2014.
    `brand_attested`: you attest grants yourself through that endpoint.
    Read-only here: the mode is operator-set; contact support to change it.
    Opt-outs are honored in both modes; the mode governs only who may
    record a GRANT.

    - `"linq_managed"`

    - `"brand_attested"`

  - `display: string`

    The brand's display name.

  - `escalation_handling: "managed" or "self_managed"`

    Who runs escalation for this brand: your stored choice, exactly as you
    set it, and `self_managed` until you set one. See `escalation_handling`
    on the update request for what each value means. While
    `escalation_handling_locked` is `true` this stored value is not
    operative: we have forced the brand to `managed` escalation.

    - `"managed"`

    - `"self_managed"`

  - `escalation_handling_locked: boolean`

    `true` while we hold this brand on `managed` escalation regardless of
    its stored choice. It is the state we use when monitoring shows a
    partner-run human path not answering. Updates to `escalation_handling` are
    refused while it is `true`; contact support to release it.

  - `link_origin: string`

    The link's origin (`seed` | `connect` | `control`), `""` when no channel is linked.

  - `managed: array of ManagedSetting`

    The read-only groups, as display strings with a managed-by marker.

    - `managed_by: "linq"`

      Who changes it. Always `linq` today: these are registered with the platform as well as with us, so they change together; write to your support contact.

      - `"linq"`

    - `name: "apple_pay_merchant" or "app_extensions" or "oauth"`

      Which setting: the Apple Pay merchant, the registered iMessage apps, or customer authentication (OAuth).

      - `"apple_pay_merchant"`

      - `"app_extensions"`

      - `"oauth"`

    - `set: boolean`

      Whether the group is operative for a send, evaluated on the RESOLVED
      view: your brand's stored configuration when present, the deploy
      default otherwise. `false` means a send needing this group is refused
      (`409`) until it is configured.

    - `source: "brand" or "platform"`

      Which copy is operative: `brand` when your brand's stored configuration answers, `platform` when the deploy default does.

      - `"brand"`

      - `"platform"`

    - `value: string`

      A display string; `merchant.com.example`, `2 registered`, `configured`,
      `not configured`. Never key material, secrets or certificate references;
      this field is safe to render verbatim.

  - `policy_version: number`

    Monotonic policy version. Bumping it (see the update's `bump_policy_version`) is what re-notifies customers of a policy change, so it only ever moves explicitly.

  - `privacy_policy_url: string`

    Absolute `https` URL of the privacy policy, `""` when unset.

  - `richlink_default_image_att_id: string`

    The brand's rich-link default card image (`att_…`), `""` when the platform default stands.

  - `support_contact: string`

    The brand's support contact, `""` when unset.

  - `terms_of_use_url: string`

    Absolute `https` URL of the terms of use, `""` when unset.

  - `apple_pay: optional object { display_name, domain, merchant_id, merchant_identity_cert_set }`

    The brand's Apple Pay configuration, non-secret fields only. ABSENT when
    the group is not configured; never an object of empty strings, the same
    absent-means-unconfigured promise `business_hours` makes.

    The merchant-identity certificate is NEVER returned, here or anywhere:
    it goes up write-only and lives in the secret store. `merchant_identity_cert_set`
    is the whole of what this contract says about it, so a client can offer
    "leave blank to keep the stored certificate" without learning anything.

    - `display_name: string`

      The business name Apple renders on the payment sheet.

    - `domain: string`

      The verified payment domain the merchant session is requested for.

    - `merchant_id: string`

      The Apple Pay merchant identifier (`merchant.example.brand`).

    - `merchant_identity_cert_set: boolean`

      Whether a merchant-identity certificate is on file. The certificate
      itself is never returned; omit `merchant_identity_cert_pem` on a write to
      keep the stored one.

  - `business_hours: optional unknown`

    Business hours as stored: a LIST of `["HH:MM","HH:MM"]` ranges per
    lowercase weekday key (`mon`…`sun`), so a lunch break or a split shift is
    expressible.

    ABSENT: the key is not present at all; when the brand publishes no
    hours, never `{}` and never an invented 9-5. That is the same promise
    `GET /v1/chats/{chat}`'s `brand` makes about the same document: an
    unconfigured brand has no hours model and the platform treats it as always
    open, which is a different fact from "open 24/7 by policy" and is not
    spelled the same way.

  - `hours_timezone: optional string`

    The IANA zone the hours are stated in (`America/New_York`). Absent when
    unset. Hours are never configured without it; the two keys are
    independently optional here only because a direct operator edit can leave
    a zone behind with no hours.

  - `oauth: optional object { authorize_url, client_id, client_secret_set, 4 more }`

    The brand's OAuth configuration, non-secret fields only. ABSENT when the
    group is not configured. The client secret is never returned, on the same
    terms as the certificate above.

    - `authorize_url: string`

      Where the customer is sent to authorize.

    - `client_id: string`

      The OAuth client id.

    - `client_secret_set: boolean`

      Whether a client secret is on file. The secret itself is never returned;
      omit `client_secret` on a write to keep the stored one.

    - `issuer_url: string`

      The identity provider's issuer URL.

    - `redirect_uri: string`

      The redirect the provider returns to.

    - `scope: string`

      The space-separated scopes requested.

    - `token_url: string`

      Where the authorization code is exchanged.

### Managed Setting

- `ManagedSetting object { managed_by, name, set, 2 more }`

  One channel-specific setting managed WITH Linq rather than edited here;
  reported as a display string so a settings screen can show the truth about
  what exists without pretending a brand can change it on this API.

  - `managed_by: "linq"`

    Who changes it. Always `linq` today: these are registered with the platform as well as with us, so they change together; write to your support contact.

    - `"linq"`

  - `name: "apple_pay_merchant" or "app_extensions" or "oauth"`

    Which setting: the Apple Pay merchant, the registered iMessage apps, or customer authentication (OAuth).

    - `"apple_pay_merchant"`

    - `"app_extensions"`

    - `"oauth"`

  - `set: boolean`

    Whether the group is operative for a send, evaluated on the RESOLVED
    view: your brand's stored configuration when present, the deploy
    default otherwise. `false` means a send needing this group is refused
    (`409`) until it is configured.

  - `source: "brand" or "platform"`

    Which copy is operative: `brand` when your brand's stored configuration answers, `platform` when the deploy default does.

    - `"brand"`

    - `"platform"`

  - `value: string`

    A display string; `merchant.com.example`, `2 registered`, `configured`,
    `not configured`. Never key material, secrets or certificate references;
    this field is safe to render verbatim.

# Integrations

## Set the Apple Pay integration

**put** `/v1/settings/integrations/apple_pay`

Sets your brand's Apple Pay integration: the full group in one write.

**Behavior**

- A present group always replaces the stored one wholly.
- The certificate is write-only: stored by reference, never returned.
  Omit `merchant_identity_cert_pem` to keep the stored certificate;
  HTTP 400 `code` 1061 when there is none to keep.

### Body Parameters

- `display_name: string`

  The merchant display name shown while acquiring the payment session.

- `domain: string`

  The verified Apple Pay merchant domain, host only, no scheme.

- `merchant_id: string`

  The TEXT-form Apple Pay Merchant ID (e.g. `merchant.com.example`), character-for-character as registered.

- `merchant_identity_cert_pem: optional string`

  WRITE-ONLY. The Merchant Identity credential as PEM text: at least one
  `CERTIFICATE` block and exactly one private-key block, concatenated.
  Stored by reference in the secret store: no read ever returns it.
  ABSENT keeps the certificate the stored group already holds; on a first
  write it is required (`400 secret_value_required`).

### Returns

- `BrandSettings object { agent_enabled, app_extensions, brand_id, 16 more }`

  The brand's channel settings: what a brand edits itself, plus the managed groups and the link state, read-only.

  - `agent_enabled: boolean`

    Whether your AI agent is switched on. `true` for a brand that has never
    changed it.

    While this is `false` we stop delivering webhooks to your brand's
    endpoints, so your agent is told nothing and answers nothing. Events that
    occur during the pause are **never** delivered; not held and replayed
    when you switch back on, which is what makes this safe to use during an
    incident: your agent will not wake up and answer an hour of stale
    messages.

    The mute covers your WHOLE webhook feed, not only the events an agent
    answers: `message.sent` / `message.failed`, `consent.expired` and the
    invitation-batch events travel the same fan-out, so delivery outcomes and
    invitation status go dark too and do not backfill. The read side is
    unaffected and is the during-mute channel; `GET /v1/messages/{msg}`,
    `GET /v1/invitations/{inv}` and `GET /v1/chats/{chat}/events` all still
    answer, so nothing is lost from the record, only from the push.

    It does **not** stop you sending. Your API key keeps working while the
    switch is off, which is what lets a human desk carry a conversation your
    agent is muted for.

  - `app_extensions: object { apps }`

    The registered apps that are OPERATIVE for your brand, keyed by the same
    `app_key` space `PUT /v1/settings/integrations/app_extensions` writes:
    your stored registry when you have one, the deploy default otherwise, and
    `managed[name=app_extensions].source` says which.

    **Read this before assembling a write.** The registry's precedence unit is
    the WHOLE set: a write replaces your stored registry outright, and a stored
    registry masks the deploy default entirely. So an app you leave out of a
    write stops resolving on the send path: a `200` and a silently
    unregistered app. This object is what makes a correct whole-set write
    possible: it is the complete operative registry, so send it back with your
    edits applied rather than assembling one from your own records.

    Unlike `apple_pay` and `oauth` above, this key is ALWAYS PRESENT: an
    empty `apps` is how "nothing is registered" is said, and it is safe to
    edit from. A registry is never "unconfigured" the way a merchant can be:
    one is always operative, so there is always an answer. The only way this
    key is absent is a platform build older than the field, and a client that
    cares about the difference should read an absent key as "cannot speak to
    the registry" and refuse to write the group; never as an empty one.

    App icons are never returned, on the same terms as the certificate and the
    client secret above: `app_icon_set` per entry is the whole of it.

    - `apps: unknown`

      The complete operative registry, `app_key` → app. Empty when nothing is
      registered. Every key here is a key `PUT /v1/settings/integrations/app_extensions`
      accepts, and that write replaces this whole map, so edit this object and
      send it back rather than composing one.

  - `brand_id: string`

    The brand this key is scoped to: the same id `GET /v1/me` reports.

  - `business_id: string`

    The linked business id, `""` when no channel is linked. The full row is `GET /v1/channels`.

  - `consent_mode: "linq_managed" or "brand_attested"`

    Who captures consent GRANTS for this brand. `linq_managed` (the
    default): we capture grants, and a grant attested through
    `POST /v1/consent` answers HTTP 409 `code` 2014.
    `brand_attested`: you attest grants yourself through that endpoint.
    Read-only here: the mode is operator-set; contact support to change it.
    Opt-outs are honored in both modes; the mode governs only who may
    record a GRANT.

    - `"linq_managed"`

    - `"brand_attested"`

  - `display: string`

    The brand's display name.

  - `escalation_handling: "managed" or "self_managed"`

    Who runs escalation for this brand: your stored choice, exactly as you
    set it, and `self_managed` until you set one. See `escalation_handling`
    on the update request for what each value means. While
    `escalation_handling_locked` is `true` this stored value is not
    operative: we have forced the brand to `managed` escalation.

    - `"managed"`

    - `"self_managed"`

  - `escalation_handling_locked: boolean`

    `true` while we hold this brand on `managed` escalation regardless of
    its stored choice. It is the state we use when monitoring shows a
    partner-run human path not answering. Updates to `escalation_handling` are
    refused while it is `true`; contact support to release it.

  - `link_origin: string`

    The link's origin (`seed` | `connect` | `control`), `""` when no channel is linked.

  - `managed: array of ManagedSetting`

    The read-only groups, as display strings with a managed-by marker.

    - `managed_by: "linq"`

      Who changes it. Always `linq` today: these are registered with the platform as well as with us, so they change together; write to your support contact.

      - `"linq"`

    - `name: "apple_pay_merchant" or "app_extensions" or "oauth"`

      Which setting: the Apple Pay merchant, the registered iMessage apps, or customer authentication (OAuth).

      - `"apple_pay_merchant"`

      - `"app_extensions"`

      - `"oauth"`

    - `set: boolean`

      Whether the group is operative for a send, evaluated on the RESOLVED
      view: your brand's stored configuration when present, the deploy
      default otherwise. `false` means a send needing this group is refused
      (`409`) until it is configured.

    - `source: "brand" or "platform"`

      Which copy is operative: `brand` when your brand's stored configuration answers, `platform` when the deploy default does.

      - `"brand"`

      - `"platform"`

    - `value: string`

      A display string; `merchant.com.example`, `2 registered`, `configured`,
      `not configured`. Never key material, secrets or certificate references;
      this field is safe to render verbatim.

  - `policy_version: number`

    Monotonic policy version. Bumping it (see the update's `bump_policy_version`) is what re-notifies customers of a policy change, so it only ever moves explicitly.

  - `privacy_policy_url: string`

    Absolute `https` URL of the privacy policy, `""` when unset.

  - `richlink_default_image_att_id: string`

    The brand's rich-link default card image (`att_…`), `""` when the platform default stands.

  - `support_contact: string`

    The brand's support contact, `""` when unset.

  - `terms_of_use_url: string`

    Absolute `https` URL of the terms of use, `""` when unset.

  - `apple_pay: optional object { display_name, domain, merchant_id, merchant_identity_cert_set }`

    The brand's Apple Pay configuration, non-secret fields only. ABSENT when
    the group is not configured; never an object of empty strings, the same
    absent-means-unconfigured promise `business_hours` makes.

    The merchant-identity certificate is NEVER returned, here or anywhere:
    it goes up write-only and lives in the secret store. `merchant_identity_cert_set`
    is the whole of what this contract says about it, so a client can offer
    "leave blank to keep the stored certificate" without learning anything.

    - `display_name: string`

      The business name Apple renders on the payment sheet.

    - `domain: string`

      The verified payment domain the merchant session is requested for.

    - `merchant_id: string`

      The Apple Pay merchant identifier (`merchant.example.brand`).

    - `merchant_identity_cert_set: boolean`

      Whether a merchant-identity certificate is on file. The certificate
      itself is never returned; omit `merchant_identity_cert_pem` on a write to
      keep the stored one.

  - `business_hours: optional unknown`

    Business hours as stored: a LIST of `["HH:MM","HH:MM"]` ranges per
    lowercase weekday key (`mon`…`sun`), so a lunch break or a split shift is
    expressible.

    ABSENT: the key is not present at all; when the brand publishes no
    hours, never `{}` and never an invented 9-5. That is the same promise
    `GET /v1/chats/{chat}`'s `brand` makes about the same document: an
    unconfigured brand has no hours model and the platform treats it as always
    open, which is a different fact from "open 24/7 by policy" and is not
    spelled the same way.

  - `hours_timezone: optional string`

    The IANA zone the hours are stated in (`America/New_York`). Absent when
    unset. Hours are never configured without it; the two keys are
    independently optional here only because a direct operator edit can leave
    a zone behind with no hours.

  - `oauth: optional object { authorize_url, client_id, client_secret_set, 4 more }`

    The brand's OAuth configuration, non-secret fields only. ABSENT when the
    group is not configured. The client secret is never returned, on the same
    terms as the certificate above.

    - `authorize_url: string`

      Where the customer is sent to authorize.

    - `client_id: string`

      The OAuth client id.

    - `client_secret_set: boolean`

      Whether a client secret is on file. The secret itself is never returned;
      omit `client_secret` on a write to keep the stored one.

    - `issuer_url: string`

      The identity provider's issuer URL.

    - `redirect_uri: string`

      The redirect the provider returns to.

    - `scope: string`

      The space-separated scopes requested.

    - `token_url: string`

      Where the authorization code is exchanged.

### Example

```http
curl https://messages.api.linqapp.com/v1/settings/integrations/apple_pay \
    -X PUT \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $LINQ_AMB_API_KEY" \
    -d '{
          "display_name": "CurbFare",
          "domain": "pay.example.com",
          "merchant_id": "merchant.com.example"
        }'
```

#### Response

```json
{
  "brand_id": "biz_9f2c1a",
  "display": "CurbFare",
  "privacy_policy_url": "https://example.com/privacy",
  "terms_of_use_url": "https://example.com/terms",
  "policy_version": 3,
  "support_contact": "support@example.com",
  "richlink_default_image_att_id": "",
  "agent_enabled": true,
  "escalation_handling": "self_managed",
  "escalation_handling_locked": false,
  "consent_mode": "linq_managed",
  "business_id": "a884eddf-b0ad-4be4-9c0e-071531638768",
  "link_origin": "connect",
  "managed": [
    {
      "name": "apple_pay_merchant",
      "value": "merchant.com.example",
      "managed_by": "linq",
      "set": true,
      "source": "brand"
    },
    {
      "name": "app_extensions",
      "value": "none registered",
      "managed_by": "linq",
      "set": false,
      "source": "platform"
    },
    {
      "name": "oauth",
      "value": "not configured",
      "managed_by": "linq",
      "set": false,
      "source": "platform"
    }
  ],
  "app_extensions": {
    "apps": {}
  }
}
```

## Set the OAuth integration

**put** `/v1/settings/integrations/oauth`

Sets your brand's customer-authentication (OAuth) integration: the
full group in one write.

**Behavior**

- A present group always replaces the stored one wholly.
- The client secret is write-only: stored by reference, never returned.
  Omit `client_secret` to keep the stored one; HTTP 400
  `secret_value_required` when there is none to keep.
- A write that changes `token_url` or `issuer_url` must carry
  `client_secret`: the stored secret is only ever sent to the token
  endpoint it was supplied for. HTTP 400 `code` 1061 at
  `/client_secret` when the field is omitted; the message names the
  endpoint that moved. `authorize_url`, `redirect_uri`, `client_id` and
  `scope` may change with the secret omitted.
- An operator-attributed write (`X-Cerebro-Operator` honoured) may not
  change `issuer_url`, `authorize_url`, `token_url` or `redirect_uri`
  once a group is stored: HTTP 403 `code` 2057 with `param`
  naming the first changed field, in that order. Make the write with the
  brand key alone, carrying the secret. An operator's first write, and an
  operator write that changes only `client_id` or `scope`, is allowed.

### Body Parameters

- `authorize_url: string`

  The provider's authorization endpoint.

- `client_id: string`

  The OAuth client id registered for this brand.

- `issuer_url: string`

  The provider's issuer URL.

- `redirect_uri: string`

  The redirect URI, character-for-character as registered with the provider.

- `scope: string`

  The scope string sent on the authorize request.

- `token_url: string`

  The provider's token endpoint.

- `client_secret: optional string`

  WRITE-ONLY. The OAuth client secret. Stored by reference in the secret
  store: no read ever returns it. ABSENT keeps the secret the stored
  group already holds; on a first write it is required
  (`400 secret_value_required`), and so is it on any write that changes
  `token_url` or `issuer_url`: the stored secret is never sent to a token
  endpoint chosen after it was supplied.

### Returns

- `BrandSettings object { agent_enabled, app_extensions, brand_id, 16 more }`

  The brand's channel settings: what a brand edits itself, plus the managed groups and the link state, read-only.

  - `agent_enabled: boolean`

    Whether your AI agent is switched on. `true` for a brand that has never
    changed it.

    While this is `false` we stop delivering webhooks to your brand's
    endpoints, so your agent is told nothing and answers nothing. Events that
    occur during the pause are **never** delivered; not held and replayed
    when you switch back on, which is what makes this safe to use during an
    incident: your agent will not wake up and answer an hour of stale
    messages.

    The mute covers your WHOLE webhook feed, not only the events an agent
    answers: `message.sent` / `message.failed`, `consent.expired` and the
    invitation-batch events travel the same fan-out, so delivery outcomes and
    invitation status go dark too and do not backfill. The read side is
    unaffected and is the during-mute channel; `GET /v1/messages/{msg}`,
    `GET /v1/invitations/{inv}` and `GET /v1/chats/{chat}/events` all still
    answer, so nothing is lost from the record, only from the push.

    It does **not** stop you sending. Your API key keeps working while the
    switch is off, which is what lets a human desk carry a conversation your
    agent is muted for.

  - `app_extensions: object { apps }`

    The registered apps that are OPERATIVE for your brand, keyed by the same
    `app_key` space `PUT /v1/settings/integrations/app_extensions` writes:
    your stored registry when you have one, the deploy default otherwise, and
    `managed[name=app_extensions].source` says which.

    **Read this before assembling a write.** The registry's precedence unit is
    the WHOLE set: a write replaces your stored registry outright, and a stored
    registry masks the deploy default entirely. So an app you leave out of a
    write stops resolving on the send path: a `200` and a silently
    unregistered app. This object is what makes a correct whole-set write
    possible: it is the complete operative registry, so send it back with your
    edits applied rather than assembling one from your own records.

    Unlike `apple_pay` and `oauth` above, this key is ALWAYS PRESENT: an
    empty `apps` is how "nothing is registered" is said, and it is safe to
    edit from. A registry is never "unconfigured" the way a merchant can be:
    one is always operative, so there is always an answer. The only way this
    key is absent is a platform build older than the field, and a client that
    cares about the difference should read an absent key as "cannot speak to
    the registry" and refuse to write the group; never as an empty one.

    App icons are never returned, on the same terms as the certificate and the
    client secret above: `app_icon_set` per entry is the whole of it.

    - `apps: unknown`

      The complete operative registry, `app_key` → app. Empty when nothing is
      registered. Every key here is a key `PUT /v1/settings/integrations/app_extensions`
      accepts, and that write replaces this whole map, so edit this object and
      send it back rather than composing one.

  - `brand_id: string`

    The brand this key is scoped to: the same id `GET /v1/me` reports.

  - `business_id: string`

    The linked business id, `""` when no channel is linked. The full row is `GET /v1/channels`.

  - `consent_mode: "linq_managed" or "brand_attested"`

    Who captures consent GRANTS for this brand. `linq_managed` (the
    default): we capture grants, and a grant attested through
    `POST /v1/consent` answers HTTP 409 `code` 2014.
    `brand_attested`: you attest grants yourself through that endpoint.
    Read-only here: the mode is operator-set; contact support to change it.
    Opt-outs are honored in both modes; the mode governs only who may
    record a GRANT.

    - `"linq_managed"`

    - `"brand_attested"`

  - `display: string`

    The brand's display name.

  - `escalation_handling: "managed" or "self_managed"`

    Who runs escalation for this brand: your stored choice, exactly as you
    set it, and `self_managed` until you set one. See `escalation_handling`
    on the update request for what each value means. While
    `escalation_handling_locked` is `true` this stored value is not
    operative: we have forced the brand to `managed` escalation.

    - `"managed"`

    - `"self_managed"`

  - `escalation_handling_locked: boolean`

    `true` while we hold this brand on `managed` escalation regardless of
    its stored choice. It is the state we use when monitoring shows a
    partner-run human path not answering. Updates to `escalation_handling` are
    refused while it is `true`; contact support to release it.

  - `link_origin: string`

    The link's origin (`seed` | `connect` | `control`), `""` when no channel is linked.

  - `managed: array of ManagedSetting`

    The read-only groups, as display strings with a managed-by marker.

    - `managed_by: "linq"`

      Who changes it. Always `linq` today: these are registered with the platform as well as with us, so they change together; write to your support contact.

      - `"linq"`

    - `name: "apple_pay_merchant" or "app_extensions" or "oauth"`

      Which setting: the Apple Pay merchant, the registered iMessage apps, or customer authentication (OAuth).

      - `"apple_pay_merchant"`

      - `"app_extensions"`

      - `"oauth"`

    - `set: boolean`

      Whether the group is operative for a send, evaluated on the RESOLVED
      view: your brand's stored configuration when present, the deploy
      default otherwise. `false` means a send needing this group is refused
      (`409`) until it is configured.

    - `source: "brand" or "platform"`

      Which copy is operative: `brand` when your brand's stored configuration answers, `platform` when the deploy default does.

      - `"brand"`

      - `"platform"`

    - `value: string`

      A display string; `merchant.com.example`, `2 registered`, `configured`,
      `not configured`. Never key material, secrets or certificate references;
      this field is safe to render verbatim.

  - `policy_version: number`

    Monotonic policy version. Bumping it (see the update's `bump_policy_version`) is what re-notifies customers of a policy change, so it only ever moves explicitly.

  - `privacy_policy_url: string`

    Absolute `https` URL of the privacy policy, `""` when unset.

  - `richlink_default_image_att_id: string`

    The brand's rich-link default card image (`att_…`), `""` when the platform default stands.

  - `support_contact: string`

    The brand's support contact, `""` when unset.

  - `terms_of_use_url: string`

    Absolute `https` URL of the terms of use, `""` when unset.

  - `apple_pay: optional object { display_name, domain, merchant_id, merchant_identity_cert_set }`

    The brand's Apple Pay configuration, non-secret fields only. ABSENT when
    the group is not configured; never an object of empty strings, the same
    absent-means-unconfigured promise `business_hours` makes.

    The merchant-identity certificate is NEVER returned, here or anywhere:
    it goes up write-only and lives in the secret store. `merchant_identity_cert_set`
    is the whole of what this contract says about it, so a client can offer
    "leave blank to keep the stored certificate" without learning anything.

    - `display_name: string`

      The business name Apple renders on the payment sheet.

    - `domain: string`

      The verified payment domain the merchant session is requested for.

    - `merchant_id: string`

      The Apple Pay merchant identifier (`merchant.example.brand`).

    - `merchant_identity_cert_set: boolean`

      Whether a merchant-identity certificate is on file. The certificate
      itself is never returned; omit `merchant_identity_cert_pem` on a write to
      keep the stored one.

  - `business_hours: optional unknown`

    Business hours as stored: a LIST of `["HH:MM","HH:MM"]` ranges per
    lowercase weekday key (`mon`…`sun`), so a lunch break or a split shift is
    expressible.

    ABSENT: the key is not present at all; when the brand publishes no
    hours, never `{}` and never an invented 9-5. That is the same promise
    `GET /v1/chats/{chat}`'s `brand` makes about the same document: an
    unconfigured brand has no hours model and the platform treats it as always
    open, which is a different fact from "open 24/7 by policy" and is not
    spelled the same way.

  - `hours_timezone: optional string`

    The IANA zone the hours are stated in (`America/New_York`). Absent when
    unset. Hours are never configured without it; the two keys are
    independently optional here only because a direct operator edit can leave
    a zone behind with no hours.

  - `oauth: optional object { authorize_url, client_id, client_secret_set, 4 more }`

    The brand's OAuth configuration, non-secret fields only. ABSENT when the
    group is not configured. The client secret is never returned, on the same
    terms as the certificate above.

    - `authorize_url: string`

      Where the customer is sent to authorize.

    - `client_id: string`

      The OAuth client id.

    - `client_secret_set: boolean`

      Whether a client secret is on file. The secret itself is never returned;
      omit `client_secret` on a write to keep the stored one.

    - `issuer_url: string`

      The identity provider's issuer URL.

    - `redirect_uri: string`

      The redirect the provider returns to.

    - `scope: string`

      The space-separated scopes requested.

    - `token_url: string`

      Where the authorization code is exchanged.

### Example

```http
curl https://messages.api.linqapp.com/v1/settings/integrations/oauth \
    -X PUT \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $LINQ_AMB_API_KEY" \
    -d '{
          "authorize_url": "https://id.example.com/authorize",
          "client_id": "curbfare-messages",
          "issuer_url": "https://id.example.com",
          "redirect_uri": "https://auth.example.com/auth/callback",
          "scope": "openid",
          "token_url": "https://id.example.com/token"
        }'
```

#### Response

```json
{
  "brand_id": "biz_9f2c1a",
  "display": "CurbFare",
  "privacy_policy_url": "https://example.com/privacy",
  "terms_of_use_url": "https://example.com/terms",
  "policy_version": 3,
  "support_contact": "support@example.com",
  "richlink_default_image_att_id": "",
  "agent_enabled": true,
  "escalation_handling": "self_managed",
  "escalation_handling_locked": false,
  "consent_mode": "linq_managed",
  "business_id": "a884eddf-b0ad-4be4-9c0e-071531638768",
  "link_origin": "connect",
  "managed": [
    {
      "name": "apple_pay_merchant",
      "value": "not configured",
      "managed_by": "linq",
      "set": false,
      "source": "platform"
    },
    {
      "name": "app_extensions",
      "value": "none registered",
      "managed_by": "linq",
      "set": false,
      "source": "platform"
    },
    {
      "name": "oauth",
      "value": "configured",
      "managed_by": "linq",
      "set": true,
      "source": "brand"
    }
  ],
  "app_extensions": {
    "apps": {}
  }
}
```

## Set the iMessage-app integration

**put** `/v1/settings/integrations/app_extensions`

Sets your brand's iMessage-app registry: the full registry in one
write.

**Behavior**

- A present registry replaces the stored one wholly: send the complete
  app set every time, never a partial update. `{"apps": {}}` is a legal
  write meaning "this brand registers no apps".
- Icons are write-only: stored by reference, never returned. Omit an
  entry's `app_icon_b64` to keep that `app_key`'s stored icon; HTTP 400
  `secret_value_required` when there is none to keep.

### Body Parameters

- `apps: unknown`

  The brand's complete registry. `{}` registers no apps (and masks any deploy default).

### Returns

- `BrandSettings object { agent_enabled, app_extensions, brand_id, 16 more }`

  The brand's channel settings: what a brand edits itself, plus the managed groups and the link state, read-only.

  - `agent_enabled: boolean`

    Whether your AI agent is switched on. `true` for a brand that has never
    changed it.

    While this is `false` we stop delivering webhooks to your brand's
    endpoints, so your agent is told nothing and answers nothing. Events that
    occur during the pause are **never** delivered; not held and replayed
    when you switch back on, which is what makes this safe to use during an
    incident: your agent will not wake up and answer an hour of stale
    messages.

    The mute covers your WHOLE webhook feed, not only the events an agent
    answers: `message.sent` / `message.failed`, `consent.expired` and the
    invitation-batch events travel the same fan-out, so delivery outcomes and
    invitation status go dark too and do not backfill. The read side is
    unaffected and is the during-mute channel; `GET /v1/messages/{msg}`,
    `GET /v1/invitations/{inv}` and `GET /v1/chats/{chat}/events` all still
    answer, so nothing is lost from the record, only from the push.

    It does **not** stop you sending. Your API key keeps working while the
    switch is off, which is what lets a human desk carry a conversation your
    agent is muted for.

  - `app_extensions: object { apps }`

    The registered apps that are OPERATIVE for your brand, keyed by the same
    `app_key` space `PUT /v1/settings/integrations/app_extensions` writes:
    your stored registry when you have one, the deploy default otherwise, and
    `managed[name=app_extensions].source` says which.

    **Read this before assembling a write.** The registry's precedence unit is
    the WHOLE set: a write replaces your stored registry outright, and a stored
    registry masks the deploy default entirely. So an app you leave out of a
    write stops resolving on the send path: a `200` and a silently
    unregistered app. This object is what makes a correct whole-set write
    possible: it is the complete operative registry, so send it back with your
    edits applied rather than assembling one from your own records.

    Unlike `apple_pay` and `oauth` above, this key is ALWAYS PRESENT: an
    empty `apps` is how "nothing is registered" is said, and it is safe to
    edit from. A registry is never "unconfigured" the way a merchant can be:
    one is always operative, so there is always an answer. The only way this
    key is absent is a platform build older than the field, and a client that
    cares about the difference should read an absent key as "cannot speak to
    the registry" and refuse to write the group; never as an empty one.

    App icons are never returned, on the same terms as the certificate and the
    client secret above: `app_icon_set` per entry is the whole of it.

    - `apps: unknown`

      The complete operative registry, `app_key` → app. Empty when nothing is
      registered. Every key here is a key `PUT /v1/settings/integrations/app_extensions`
      accepts, and that write replaces this whole map, so edit this object and
      send it back rather than composing one.

  - `brand_id: string`

    The brand this key is scoped to: the same id `GET /v1/me` reports.

  - `business_id: string`

    The linked business id, `""` when no channel is linked. The full row is `GET /v1/channels`.

  - `consent_mode: "linq_managed" or "brand_attested"`

    Who captures consent GRANTS for this brand. `linq_managed` (the
    default): we capture grants, and a grant attested through
    `POST /v1/consent` answers HTTP 409 `code` 2014.
    `brand_attested`: you attest grants yourself through that endpoint.
    Read-only here: the mode is operator-set; contact support to change it.
    Opt-outs are honored in both modes; the mode governs only who may
    record a GRANT.

    - `"linq_managed"`

    - `"brand_attested"`

  - `display: string`

    The brand's display name.

  - `escalation_handling: "managed" or "self_managed"`

    Who runs escalation for this brand: your stored choice, exactly as you
    set it, and `self_managed` until you set one. See `escalation_handling`
    on the update request for what each value means. While
    `escalation_handling_locked` is `true` this stored value is not
    operative: we have forced the brand to `managed` escalation.

    - `"managed"`

    - `"self_managed"`

  - `escalation_handling_locked: boolean`

    `true` while we hold this brand on `managed` escalation regardless of
    its stored choice. It is the state we use when monitoring shows a
    partner-run human path not answering. Updates to `escalation_handling` are
    refused while it is `true`; contact support to release it.

  - `link_origin: string`

    The link's origin (`seed` | `connect` | `control`), `""` when no channel is linked.

  - `managed: array of ManagedSetting`

    The read-only groups, as display strings with a managed-by marker.

    - `managed_by: "linq"`

      Who changes it. Always `linq` today: these are registered with the platform as well as with us, so they change together; write to your support contact.

      - `"linq"`

    - `name: "apple_pay_merchant" or "app_extensions" or "oauth"`

      Which setting: the Apple Pay merchant, the registered iMessage apps, or customer authentication (OAuth).

      - `"apple_pay_merchant"`

      - `"app_extensions"`

      - `"oauth"`

    - `set: boolean`

      Whether the group is operative for a send, evaluated on the RESOLVED
      view: your brand's stored configuration when present, the deploy
      default otherwise. `false` means a send needing this group is refused
      (`409`) until it is configured.

    - `source: "brand" or "platform"`

      Which copy is operative: `brand` when your brand's stored configuration answers, `platform` when the deploy default does.

      - `"brand"`

      - `"platform"`

    - `value: string`

      A display string; `merchant.com.example`, `2 registered`, `configured`,
      `not configured`. Never key material, secrets or certificate references;
      this field is safe to render verbatim.

  - `policy_version: number`

    Monotonic policy version. Bumping it (see the update's `bump_policy_version`) is what re-notifies customers of a policy change, so it only ever moves explicitly.

  - `privacy_policy_url: string`

    Absolute `https` URL of the privacy policy, `""` when unset.

  - `richlink_default_image_att_id: string`

    The brand's rich-link default card image (`att_…`), `""` when the platform default stands.

  - `support_contact: string`

    The brand's support contact, `""` when unset.

  - `terms_of_use_url: string`

    Absolute `https` URL of the terms of use, `""` when unset.

  - `apple_pay: optional object { display_name, domain, merchant_id, merchant_identity_cert_set }`

    The brand's Apple Pay configuration, non-secret fields only. ABSENT when
    the group is not configured; never an object of empty strings, the same
    absent-means-unconfigured promise `business_hours` makes.

    The merchant-identity certificate is NEVER returned, here or anywhere:
    it goes up write-only and lives in the secret store. `merchant_identity_cert_set`
    is the whole of what this contract says about it, so a client can offer
    "leave blank to keep the stored certificate" without learning anything.

    - `display_name: string`

      The business name Apple renders on the payment sheet.

    - `domain: string`

      The verified payment domain the merchant session is requested for.

    - `merchant_id: string`

      The Apple Pay merchant identifier (`merchant.example.brand`).

    - `merchant_identity_cert_set: boolean`

      Whether a merchant-identity certificate is on file. The certificate
      itself is never returned; omit `merchant_identity_cert_pem` on a write to
      keep the stored one.

  - `business_hours: optional unknown`

    Business hours as stored: a LIST of `["HH:MM","HH:MM"]` ranges per
    lowercase weekday key (`mon`…`sun`), so a lunch break or a split shift is
    expressible.

    ABSENT: the key is not present at all; when the brand publishes no
    hours, never `{}` and never an invented 9-5. That is the same promise
    `GET /v1/chats/{chat}`'s `brand` makes about the same document: an
    unconfigured brand has no hours model and the platform treats it as always
    open, which is a different fact from "open 24/7 by policy" and is not
    spelled the same way.

  - `hours_timezone: optional string`

    The IANA zone the hours are stated in (`America/New_York`). Absent when
    unset. Hours are never configured without it; the two keys are
    independently optional here only because a direct operator edit can leave
    a zone behind with no hours.

  - `oauth: optional object { authorize_url, client_id, client_secret_set, 4 more }`

    The brand's OAuth configuration, non-secret fields only. ABSENT when the
    group is not configured. The client secret is never returned, on the same
    terms as the certificate above.

    - `authorize_url: string`

      Where the customer is sent to authorize.

    - `client_id: string`

      The OAuth client id.

    - `client_secret_set: boolean`

      Whether a client secret is on file. The secret itself is never returned;
      omit `client_secret` on a write to keep the stored one.

    - `issuer_url: string`

      The identity provider's issuer URL.

    - `redirect_uri: string`

      The redirect the provider returns to.

    - `scope: string`

      The space-separated scopes requested.

    - `token_url: string`

      Where the authorization code is exchanged.

### Example

```http
curl https://messages.api.linqapp.com/v1/settings/integrations/app_extensions \
    -X PUT \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $LINQ_AMB_API_KEY" \
    -d '{
          "apps": {
            "payments": {
              "bid": "com.apple.messages.MSMessageExtensionBalloonPlugin:ABCDE12345:com.example.app.MessagesExtension",
              "app_id": "6794029060",
              "app_name": "CurbFare Payments",
              "app_icon_b64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR42mNgAAIAAAUAAen63NgAAAAASUVORK5CYII="
            }
          }
        }'
```

#### Response

```json
{
  "brand_id": "biz_9f2c1a",
  "display": "CurbFare",
  "privacy_policy_url": "https://example.com/privacy",
  "terms_of_use_url": "https://example.com/terms",
  "policy_version": 3,
  "support_contact": "support@example.com",
  "richlink_default_image_att_id": "",
  "agent_enabled": true,
  "escalation_handling": "self_managed",
  "escalation_handling_locked": false,
  "consent_mode": "linq_managed",
  "business_id": "a884eddf-b0ad-4be4-9c0e-071531638768",
  "link_origin": "connect",
  "managed": [
    {
      "name": "apple_pay_merchant",
      "value": "not configured",
      "managed_by": "linq",
      "set": false,
      "source": "platform"
    },
    {
      "name": "app_extensions",
      "value": "1 registered",
      "managed_by": "linq",
      "set": true,
      "source": "brand"
    },
    {
      "name": "oauth",
      "value": "not configured",
      "managed_by": "linq",
      "set": false,
      "source": "platform"
    }
  ],
  "app_extensions": {
    "apps": {
      "ride": {
        "bid": "com.apple.messages.MSMessageExtensionBalloonPlugin:9BC1AB2D3E:com.curbfare.ride.imsg",
        "app_id": "6440000001",
        "app_name": "CurbFare Ride",
        "app_icon_set": true
      }
    }
  }
}
```

## Clear an integration group

**delete** `/v1/settings/integrations/{group}`

Clears one stored integration group, returning that group to the
platform default when one exists; `source` returns to `platform`.

**Behavior**

- Clearing a group that is not stored is a no-op, not an error.

**Errors**

- HTTP 404 `code` 2026: unknown group name.

### Path Parameters

- `group: "apple_pay" or "oauth" or "app_extensions"`

  - `"apple_pay"`

  - `"oauth"`

  - `"app_extensions"`

### Returns

- `BrandSettings object { agent_enabled, app_extensions, brand_id, 16 more }`

  The brand's channel settings: what a brand edits itself, plus the managed groups and the link state, read-only.

  - `agent_enabled: boolean`

    Whether your AI agent is switched on. `true` for a brand that has never
    changed it.

    While this is `false` we stop delivering webhooks to your brand's
    endpoints, so your agent is told nothing and answers nothing. Events that
    occur during the pause are **never** delivered; not held and replayed
    when you switch back on, which is what makes this safe to use during an
    incident: your agent will not wake up and answer an hour of stale
    messages.

    The mute covers your WHOLE webhook feed, not only the events an agent
    answers: `message.sent` / `message.failed`, `consent.expired` and the
    invitation-batch events travel the same fan-out, so delivery outcomes and
    invitation status go dark too and do not backfill. The read side is
    unaffected and is the during-mute channel; `GET /v1/messages/{msg}`,
    `GET /v1/invitations/{inv}` and `GET /v1/chats/{chat}/events` all still
    answer, so nothing is lost from the record, only from the push.

    It does **not** stop you sending. Your API key keeps working while the
    switch is off, which is what lets a human desk carry a conversation your
    agent is muted for.

  - `app_extensions: object { apps }`

    The registered apps that are OPERATIVE for your brand, keyed by the same
    `app_key` space `PUT /v1/settings/integrations/app_extensions` writes:
    your stored registry when you have one, the deploy default otherwise, and
    `managed[name=app_extensions].source` says which.

    **Read this before assembling a write.** The registry's precedence unit is
    the WHOLE set: a write replaces your stored registry outright, and a stored
    registry masks the deploy default entirely. So an app you leave out of a
    write stops resolving on the send path: a `200` and a silently
    unregistered app. This object is what makes a correct whole-set write
    possible: it is the complete operative registry, so send it back with your
    edits applied rather than assembling one from your own records.

    Unlike `apple_pay` and `oauth` above, this key is ALWAYS PRESENT: an
    empty `apps` is how "nothing is registered" is said, and it is safe to
    edit from. A registry is never "unconfigured" the way a merchant can be:
    one is always operative, so there is always an answer. The only way this
    key is absent is a platform build older than the field, and a client that
    cares about the difference should read an absent key as "cannot speak to
    the registry" and refuse to write the group; never as an empty one.

    App icons are never returned, on the same terms as the certificate and the
    client secret above: `app_icon_set` per entry is the whole of it.

    - `apps: unknown`

      The complete operative registry, `app_key` → app. Empty when nothing is
      registered. Every key here is a key `PUT /v1/settings/integrations/app_extensions`
      accepts, and that write replaces this whole map, so edit this object and
      send it back rather than composing one.

  - `brand_id: string`

    The brand this key is scoped to: the same id `GET /v1/me` reports.

  - `business_id: string`

    The linked business id, `""` when no channel is linked. The full row is `GET /v1/channels`.

  - `consent_mode: "linq_managed" or "brand_attested"`

    Who captures consent GRANTS for this brand. `linq_managed` (the
    default): we capture grants, and a grant attested through
    `POST /v1/consent` answers HTTP 409 `code` 2014.
    `brand_attested`: you attest grants yourself through that endpoint.
    Read-only here: the mode is operator-set; contact support to change it.
    Opt-outs are honored in both modes; the mode governs only who may
    record a GRANT.

    - `"linq_managed"`

    - `"brand_attested"`

  - `display: string`

    The brand's display name.

  - `escalation_handling: "managed" or "self_managed"`

    Who runs escalation for this brand: your stored choice, exactly as you
    set it, and `self_managed` until you set one. See `escalation_handling`
    on the update request for what each value means. While
    `escalation_handling_locked` is `true` this stored value is not
    operative: we have forced the brand to `managed` escalation.

    - `"managed"`

    - `"self_managed"`

  - `escalation_handling_locked: boolean`

    `true` while we hold this brand on `managed` escalation regardless of
    its stored choice. It is the state we use when monitoring shows a
    partner-run human path not answering. Updates to `escalation_handling` are
    refused while it is `true`; contact support to release it.

  - `link_origin: string`

    The link's origin (`seed` | `connect` | `control`), `""` when no channel is linked.

  - `managed: array of ManagedSetting`

    The read-only groups, as display strings with a managed-by marker.

    - `managed_by: "linq"`

      Who changes it. Always `linq` today: these are registered with the platform as well as with us, so they change together; write to your support contact.

      - `"linq"`

    - `name: "apple_pay_merchant" or "app_extensions" or "oauth"`

      Which setting: the Apple Pay merchant, the registered iMessage apps, or customer authentication (OAuth).

      - `"apple_pay_merchant"`

      - `"app_extensions"`

      - `"oauth"`

    - `set: boolean`

      Whether the group is operative for a send, evaluated on the RESOLVED
      view: your brand's stored configuration when present, the deploy
      default otherwise. `false` means a send needing this group is refused
      (`409`) until it is configured.

    - `source: "brand" or "platform"`

      Which copy is operative: `brand` when your brand's stored configuration answers, `platform` when the deploy default does.

      - `"brand"`

      - `"platform"`

    - `value: string`

      A display string; `merchant.com.example`, `2 registered`, `configured`,
      `not configured`. Never key material, secrets or certificate references;
      this field is safe to render verbatim.

  - `policy_version: number`

    Monotonic policy version. Bumping it (see the update's `bump_policy_version`) is what re-notifies customers of a policy change, so it only ever moves explicitly.

  - `privacy_policy_url: string`

    Absolute `https` URL of the privacy policy, `""` when unset.

  - `richlink_default_image_att_id: string`

    The brand's rich-link default card image (`att_…`), `""` when the platform default stands.

  - `support_contact: string`

    The brand's support contact, `""` when unset.

  - `terms_of_use_url: string`

    Absolute `https` URL of the terms of use, `""` when unset.

  - `apple_pay: optional object { display_name, domain, merchant_id, merchant_identity_cert_set }`

    The brand's Apple Pay configuration, non-secret fields only. ABSENT when
    the group is not configured; never an object of empty strings, the same
    absent-means-unconfigured promise `business_hours` makes.

    The merchant-identity certificate is NEVER returned, here or anywhere:
    it goes up write-only and lives in the secret store. `merchant_identity_cert_set`
    is the whole of what this contract says about it, so a client can offer
    "leave blank to keep the stored certificate" without learning anything.

    - `display_name: string`

      The business name Apple renders on the payment sheet.

    - `domain: string`

      The verified payment domain the merchant session is requested for.

    - `merchant_id: string`

      The Apple Pay merchant identifier (`merchant.example.brand`).

    - `merchant_identity_cert_set: boolean`

      Whether a merchant-identity certificate is on file. The certificate
      itself is never returned; omit `merchant_identity_cert_pem` on a write to
      keep the stored one.

  - `business_hours: optional unknown`

    Business hours as stored: a LIST of `["HH:MM","HH:MM"]` ranges per
    lowercase weekday key (`mon`…`sun`), so a lunch break or a split shift is
    expressible.

    ABSENT: the key is not present at all; when the brand publishes no
    hours, never `{}` and never an invented 9-5. That is the same promise
    `GET /v1/chats/{chat}`'s `brand` makes about the same document: an
    unconfigured brand has no hours model and the platform treats it as always
    open, which is a different fact from "open 24/7 by policy" and is not
    spelled the same way.

  - `hours_timezone: optional string`

    The IANA zone the hours are stated in (`America/New_York`). Absent when
    unset. Hours are never configured without it; the two keys are
    independently optional here only because a direct operator edit can leave
    a zone behind with no hours.

  - `oauth: optional object { authorize_url, client_id, client_secret_set, 4 more }`

    The brand's OAuth configuration, non-secret fields only. ABSENT when the
    group is not configured. The client secret is never returned, on the same
    terms as the certificate above.

    - `authorize_url: string`

      Where the customer is sent to authorize.

    - `client_id: string`

      The OAuth client id.

    - `client_secret_set: boolean`

      Whether a client secret is on file. The secret itself is never returned;
      omit `client_secret` on a write to keep the stored one.

    - `issuer_url: string`

      The identity provider's issuer URL.

    - `redirect_uri: string`

      The redirect the provider returns to.

    - `scope: string`

      The space-separated scopes requested.

    - `token_url: string`

      Where the authorization code is exchanged.

### Example

```http
curl https://messages.api.linqapp.com/v1/settings/integrations/$GROUP \
    -X DELETE \
    -H "Authorization: Bearer $LINQ_AMB_API_KEY"
```

#### Response

```json
{
  "brand_id": "biz_9f2c1a",
  "display": "CurbFare",
  "privacy_policy_url": "https://example.com/privacy",
  "terms_of_use_url": "https://example.com/terms",
  "policy_version": 3,
  "support_contact": "support@example.com",
  "richlink_default_image_att_id": "",
  "agent_enabled": true,
  "escalation_handling": "self_managed",
  "escalation_handling_locked": false,
  "consent_mode": "linq_managed",
  "business_id": "a884eddf-b0ad-4be4-9c0e-071531638768",
  "link_origin": "connect",
  "managed": [
    {
      "name": "apple_pay_merchant",
      "value": "not configured",
      "managed_by": "linq",
      "set": false,
      "source": "platform"
    },
    {
      "name": "app_extensions",
      "value": "none registered",
      "managed_by": "linq",
      "set": false,
      "source": "platform"
    },
    {
      "name": "oauth",
      "value": "not configured",
      "managed_by": "linq",
      "set": false,
      "source": "platform"
    }
  ],
  "app_extensions": {
    "apps": {}
  }
}
```
