API Reference
Channels and Integrations
💬Get free consultation

Channels and integrations

ⓘ

Who can use this feature?

  • An admin on Pro or Plus can create the calling API key. These routes require explicit scopes; legacy unscoped keys are refused.
  • Reads need settings:read. Every write needs settings.integrations:write (or the broader settings:write). Treat this scope as sensitive: it can disconnect channels your customers use.
  • Connections that need the merchant to sign in with the provider cannot be started through the API. The read responses give a connectUrl for those.

All paths use the Public API base URL. Send X-Api-Key on every request and Content-Type: application/json when the table shows a JSON body. These routes are not available under /chat.

Shared rules

  • Write bodies are strict. An unknown field, a wrong type or a missing required field returns 400 VALIDATION_FAILED. Routes without a body accept no body or {}.
  • Successful writes return {"success":true,"data":...}. When there is nothing to return, the body is {"success":true}.
  • Responses never contain credentials. Tokens, API keys, webhook URLs, pairing codes and secrets are removed at any depth before the response is sent.
  • An ID from another store returns 404 NOT_FOUND, the same as an unknown ID.
  • Every write, successful or not, is recorded in the store's audit log with the calling key as actor. Writes are not idempotent; read the status before repeating an uncertain request.

Read connection status

Both reads take no body and return 200. They return connection state, counts and links only.

MethodPathScopeReturns
GET/channelssettings:reademail, facebook, instagram, whatsapp
GET/integrationssettings:readjoy, tracking, klaviyo, zendesk, gorgias, discord, telegram, seaSurvey
{
  "data": {
    "email": {
      "connected": true,
      "count": 2,
      "countUnit": "configured connections",
      "description": "Connection state and configured-connection count only; no credentials are returned.",
      "adminUrl": "/settings/channels/email",
      "connectUrl": "https://admin.shopify.com/store/shop/apps/chatty/embed/settings/channels/email"
    },
    "facebook": {
      "connected": false,
      "count": 0,
      "countUnit": "configured connections",
      "adminUrl": "/settings/channels/facebook",
      "requiresMerchantAction": true,
      "connectUrl": "https://admin.shopify.com/store/shop/apps/chatty/embed/settings/channels/facebook"
    }
  }
}

The example is an excerpt of GET /channels. Each channel has connected, count, countUnit, description, adminUrl and connectUrl. count is the number of configured connections, not the number subscribed. An email channel counts as connected once at least one address is verified.

GET /integrations returns one entry per integration with connected, description, adminUrl and connectUrl. tracking also has provider when an order-tracking provider is set. Some providers have their own rules for connected or their response key; see the provider notes.

requiresMerchantAction: true means a person must finish the connection in the Chatty admin. Send them to connectUrl; the API cannot start that sign-in. A missing store returns 404 NOT_FOUND.

GET /channels returns counts, not channel IDs. Get the page, Instagram account, phone number or email channel ID from the Chatty admin, or keep the ID returned when you create an email channel.

Channel preferences

All routes need settings.integrations:write and return 200 unless noted.

MethodPathJSON requestSuccess response
PATCH/channels/facebook/pages/{pageId}{"subscribed":true}{"success":true}
DELETE/channels/facebookNo body{"success":true,"data":true}
PATCH/channels/instagram/{id}{"subscribed":false}{"success":true}
PATCH/channels/whatsapp/{phoneId}{"catalogOrderEnabled":true}{"success":true,"data":{"id":"phone_123","catalogOrderEnabled":true}}
DELETE/channels/whatsapp/{phoneId}No body{"success":true}
  • subscribed turns message handling on or off for one connected page or Instagram account. It does not disconnect it.
  • catalogOrderEnabled turns catalog order handling on or off for one phone number. The response is the stored phone record without its access token or PIN; the example is an excerpt.
  • DELETE /channels/facebook disconnects every connected page of the store, and the Instagram accounts linked to those pages. There is no route that disconnects a single page or a single Instagram account. If some pages cannot be disconnected, the route returns 502 with "Some Facebook pages could not be disconnected"; pages already removed stay removed, and repeating the request is safe.
  • DELETE /channels/whatsapp/{phoneId} removes one phone number. When it was the last number on its business account, the account is unsubscribed too.
  • Reconnecting a removed page, Instagram account or phone number needs the merchant in the admin.

Errors: 404 NOT_FOUND with Page not found, Instagram account not found or Phone number not found; 400 VALIDATION_FAILED for an invalid body.

Email channels

MethodPathJSON requestSuccess response
POST/channels/email{"email":"[email protected]","provider":"gmail","createMode":"ticket"}201 {"success":true,"data":{"id":"email_channel_123"}}
PATCH/channels/email/{id}{"createMode":"conversation"}200 {"success":true}
DELETE/channels/email/{id}No body200 {"success":true}
POST/channels/email/sender-domain/recheckNo body200, sender domain record

email must be a valid address and is stored in lowercase. provider is a free-text label of 1–100 characters, such as the mail service name. createMode is conversation or ticket; it is the only field PATCH can change. A new address starts unverified and becomes verified when forwarded mail arrives, as set up in the admin. Deletion removes the channel immediately.

On the free plan, a store that already has one email channel cannot add another: 403 FORBIDDEN. An address that matches an existing one, including dot or plus variants, returns 409 CONFLICT with Email address is duplicated. An unknown ID returns 404 NOT_FOUND.

The recheck route checks the DNS records of the store's custom sender domain again and returns the record:

{"success":true,"data":{"domain":"example.com","dkimStatus":"PENDING","dkimTokens":["token1","token2","token3"],"dmarcPolicy":"none","permanentReason":"","shopCount":1,"dkimRecheckAt":"2026-10-23T08:00:00.000Z"}}

The example is an excerpt. dkimStatus is PENDING, SUCCESS or FAILED. dkimTokens are the public DNS values to publish. A store without a verified custom sender domain returns 404 NOT_FOUND with Sender domain not found. A recheck within 60 seconds of the previous one returns 429 RATE_LIMITED with a Retry-After header and a top-level retryAfter in seconds.

Integrations

All four routes need settings.integrations:write. {app} must support the operation, or the route returns 400 INVALID_PARAMS with This integration does not support the requested operation. An unknown {app} returns 400 VALIDATION_FAILED.

MethodPathOperationSuccess
POST/integrations/{app}Connect with a credential200
PATCH/integrations/{app}Change an integration setting200
POST/integrations/{app}/verifyCheck a credential without saving it200
DELETE/integrations/{app}Disconnect and clear stored credentials200

Supported providers

Get each credential from the provider's own documentation, linked below.

{app}ConnectUpdateVerifyDisconnectCredential
klaviyo{"privateToken":"..."}——YesKlaviyo private API key
discord{"webhookUrl":"..."}——YesDiscord webhook URL
sea-surveyAdmin only{"customerSync":true}—YesConnected by the merchant
17track——{"apiKey":"..."}—17TRACK security key
joyAdmin only——YesConnected by the merchant
zendeskAdmin only——YesConnected by the merchant
gorgiasAdmin only——YesConnected by the merchant
telegramAdmin only——YesPaired by the merchant

Where to get each credential:

"Admin only" connections use the provider's sign-in or pairing. Send the merchant to the connectUrl from GET /integrations. The order-tracking provider itself is not writable through these routes.

Provider notes:

{app}Behavior
klaviyoprivateToken is 1–4,096 characters. A rejected key returns 400 INVALID_CREDENTIALS. Connecting replaces any earlier sign-in connection and starts a sync. The response is the stored record, for example {"id":"integration_123","app":"klaviyo","isConnected":true,"isOAuth":false,"eventsScopeError":null}.
discordwebhookUrl (up to 2,048 characters) must be the full webhook URL; anything else returns 400 INVALID_PARAMS. Chatty reads the webhook without posting a message. The response is {"webhookName":"...","channelId":"...","guildId":"..."} and never includes the URL. A deleted or invalid webhook returns 400 discord_webhook_invalid. More than 10 connect attempts per store per minute returns 429 discord_temporary; a provider outage returns 502 discord_temporary.
sea-surveyIts key in GET /integrations is seaSurvey. customerSync turns the daily customer sync on or off. Chatty changes it at the provider first, then stores it. Not connected: 403 FORBIDDEN; provider call failed: 502. Disconnecting also clears the sync setting.
17trackVerify saves nothing and returns 200 whether or not the key works, for example {"success":true,"data":{"valid":false,"error":"Invalid API key"}}. Each API key can verify 10 times per hour, shared with MCP; over the limit: 429 RATE_LIMITED. If the limit cannot be checked, 503.
telegramCounts as connected in GET /integrations only after a chat is paired. Disconnecting removes the paired chat and any pending pairing.

Connect

POST /integrations/{app} checks the credential with the provider first, then saves it. Connecting again replaces the stored credential. The body is the credential field listed for the provider.

curl -X POST 'https://app.chatty.net/v1/integrations/{app}' \
  -H 'X-Api-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{"<credential field>":"..."}'

A success returns 200 with {"success":true,"data":...}, where data never contains the credential. A credential the provider rejects returns 400; see the provider notes for the exact code.

Update and verify

PATCH /integrations/{app} changes a provider setting and returns the stored record without credentials. POST /integrations/{app}/verify checks a credential and saves nothing. Which providers support each, the body they take, and their limits are in the tables above.

Disconnect

DELETE /integrations/{app} takes no body. It revokes the credential at the provider where supported, then clears the stored credentials. Repeating it is safe. The response is {"success":true} or the cleared record without credentials, for example {"success":true,"data":{"id":"integration_123","isConnected":false}}. Reconnecting an "Admin only" provider needs the merchant again.

Errors

HTTPCodeMeaning
400VALIDATION_FAILEDInvalid body, unknown field, or unknown {app}.
400INVALID_PARAMSThe provider does not support this operation, or the credential has the wrong format.
400INVALID_CREDENTIALSThe provider rejected the credential.
401UNAUTHENTICATED / INVALID_API_KEYMissing, unknown or revoked key.
403FORBIDDENMissing scope, legacy key, email channel limit on the free plan, or the integration is not connected.
404NOT_FOUNDUnknown or foreign channel ID, or no custom sender domain.
409CONFLICTDuplicate email address.
429RATE_LIMITEDShared Public API limit, recheck cooldown, or a provider-specific limit above.
502 / 503INVALID_PARAMS or provider codeThe provider or a limit check failed. Some pages may already be disconnected; read the status before retrying.

On MCP, get_channels and get_integrations read the status, and manage_integrations performs these writes with an API key. See the MCP tool reference.