## 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": {}
  }
}
```
