Skip to content
Linq

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:0024: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 ParametersJSONExpand Collapse
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.

One of the following:
"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.

support_contact: optional string

Free text, trimmed; stored to 320 characters.

terms_of_use_url: optional string

Same rules as privacy_policy_url.

ReturnsExpand Collapse
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.

One of the following:
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.

One of the following:
"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.

managed: array of ManagedSetting { managed_by, name, set, 2 more }

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.

name: "apple_pay_merchant" or "app_extensions" or "oauth"

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

One of the following:
"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.

One of the following:
"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.

formatint32
privacy_policy_url: string

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

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 (monsun), 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.

Update channel settings

curl https://messages.api.linqapp.com/v1/settings \
    -X PUT \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $LINQ_AMB_API_KEY" \
    -d '{}'
{
  "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": "[email protected]",
  "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": {}
  }
}
{
  "brand_id": "biz_9f2c1a",
  "display": "CurbFare",
  "privacy_policy_url": "https://example.com/privacy",
  "terms_of_use_url": "https://example.com/terms",
  "policy_version": 4,
  "support_contact": "[email protected]",
  "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": {}
  }
}
Returns Examples
{
  "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": "[email protected]",
  "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": {}
  }
}
{
  "brand_id": "biz_9f2c1a",
  "display": "CurbFare",
  "privacy_policy_url": "https://example.com/privacy",
  "terms_of_use_url": "https://example.com/terms",
  "policy_version": 4,
  "support_contact": "[email protected]",
  "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": {}
  }
}