Your AI Connector Docs

Channel Connection API

This guide shows you how to connect messaging channels to an account using the API. It is written for a developer building an integration or wrapper, so it focuses on the exact requests, the order to make them in, and the responses you get back.

There is one pattern you need to understand up front, because it applies to almost every channel here.

The connect-then-poll pattern

Most channels cannot be connected with a single API call. Connecting WhatsApp, Instagram, or Messenger means the account holder has to log in to their own provider account and approve access. There is no headless (fully automated) path for that approval - a real person has to open a URL in a browser, or scan a QR code with their phone.

So the flow is always:

  1. Start the connection with a POST. The response gives you either a URL to open, or a QR code to display.
  2. Hand that off to the end user - open the URL in their browser, or render the QR code on screen for them to scan.
  3. Poll the status endpoint with GET on a short interval (every few seconds) until the status reaches a connected state.

Your integration’s job is to drive that loop: show the URL or QR, then poll until done. Plan your UI around the poll - a spinner with a “waiting for you to finish in your browser” message works well.

Note: Before you start, make sure API access is enabled on the plan and you have an API key. See API Access for how to generate one. All requests below use the base URL https://api.youraiconnector.com/v1 and you must authenticate every request. See Authentication for the four accepted forms - the examples here use the X-API-Key header, with one cURL example per page showing the simpler ?apiKey= query form.


Instagram + Messenger (Meta)

Instagram and Messenger are connected together in one flow, because they both run on a Facebook Page. The account holder authorizes through Facebook, you fetch the list of Pages they manage, and you pick which Page to connect.

Step 1 - Start the Instagram + Messenger connection

POST /channels/meta/connect

This returns a consent URL. No credentials are sent in this request - the connection is authorized entirely in the browser.

cURL

curl -X POST "https://api.youraiconnector.com/v1/channels/meta/connect?apiKey=YOUR_API_KEY"

JavaScript

const res = await fetch("https://api.youraiconnector.com/v1/channels/meta/connect", {
  method: "POST",
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
// Open data.oauth_url in the end user's browser.

Python

import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/channels/meta/connect",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
# Open data["oauth_url"] in the end user's browser.

Response

{
  "success": true,
  "oauth_url": "https://www.facebook.com/v21.0/dialog/oauth?client_id=...&state=...",
  "state_token": "opaque-one-time-token",
  "connect_url": "https://api.youraiconnector.com/v1/channels/meta/connect/page?token=eyJhbGciOi...",
  "connect_url_expires_at": 1717000000000,
  "expires_at": "2026-06-10T12:30:00.000Z"
}

Open oauth_url in the end user’s browser so they can log in to Facebook and approve access. The connection attempt expires at expires_at (about 30 minutes) - if it lapses, start over. Treat state_token as a short-lived secret and do not log it.

Easiest option for Instagram + Messenger: hand over connect_url

The response also includes a ready-made connect_url: a hosted page that runs the whole flow for the account holder. They open it, log in to Facebook, and when they have more than one Page it shows the list and lets them pick which one to connect - then it reports success on its own. Give this link to the account holder instead of opening oauth_url yourself, building a Page picker, and polling. The link works for about 30 minutes (connect_url_expires_at); if it lapses, start a new connection. The manual steps below are for integrations that want to drive the flow and render the Page picker themselves.

Step 2 - Poll the status until pages load

GET /channels/meta/status

After the user finishes the Facebook login, poll this endpoint every few seconds. The status field walks through these steps:

status Meaning
pending Consent not completed yet. Keep waiting.
token_received Authorized, but the list of Pages is still loading.
pages_loaded Pages are available - move to step 3.
connected A Page has been selected and the channel is live.

cURL

curl "https://api.youraiconnector.com/v1/channels/meta/status" \
  -H "X-API-Key: YOUR_API_KEY"

JavaScript

const res = await fetch("https://api.youraiconnector.com/v1/channels/meta/status", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
// Poll until data.status === "pages_loaded".

Python

res = requests.get(
    "https://api.youraiconnector.com/v1/channels/meta/status",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
# Poll until data["status"] == "pages_loaded".

Response (once pages have loaded)

{
  "success": true,
  "status": "pages_loaded",
  "pages": [
    {
      "id": "1234567890",
      "name": "My Business Page",
      "category": "Local business",
      "instagram_business_account": {
        "id": "17890000000000000",
        "username": "mybusiness"
      }
    }
  ],
  "selected_page": null
}

Step 3 - List the pages (optional)

If you’d rather fetch the Page list on its own (for example, to render a picker), use:

GET /channels/meta/pages
curl "https://api.youraiconnector.com/v1/channels/meta/pages" \
  -H "X-API-Key: YOUR_API_KEY"

It returns the same pages array as the status endpoint. (The status endpoint already includes the pages, so this call is just a convenience.)

Step 4 - Select the page to connect

POST /channels/meta/select-page

Send the page_id of the Page the user chose. The Instagram account linked to that Page is connected automatically; you only need the instagram object if you want to override which Instagram account to use.

cURL

curl -X POST "https://api.youraiconnector.com/v1/channels/meta/select-page" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "page_id": "1234567890" }'

JavaScript

const res = await fetch("https://api.youraiconnector.com/v1/channels/meta/select-page", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ page_id: "1234567890" }),
});
const data = await res.json();

Python

res = requests.post(
    "https://api.youraiconnector.com/v1/channels/meta/select-page",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"page_id": "1234567890"},
)
data = res.json()

Response

{
  "success": true,
  "page_id": "1234567890",
  "instagram_business_account_id": "17890000000000000"
}

The channel is now connected. A follow-up GET /channels/meta/status will report status: "connected".

List the connected page’s posts

GET /channels/meta/posts?platform=instagram

Returns the recent posts of the page you connected - Instagram media or Facebook posts. This is what you render a picker from when you set up an Entry Point that reacts to comments on one specific post.

Query parameter Required Description
platform Yes instagram or facebook. Anything else returns a 400.
limit No How many posts to return, 1-50. Defaults to 25.
after No Cursor for the next page - pass the nextCursor value from the previous response.

cURL

curl "https://api.youraiconnector.com/v1/channels/meta/posts?platform=instagram&limit=25" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{
  "success": true,
  "connected": true,
  "platform": "instagram",
  "posts": [
    {
      "id": "17900000000000000",
      "caption": "New spring menu is live",
      "thumbnailUrl": "https://scontent.cdninstagram.com/...",
      "permalink": "https://www.instagram.com/p/Cxxxxxxxxxx/",
      "createdAt": "2026-05-02T09:12:00.000Z",
      "mediaType": "REELS"
    }
  ],
  "nextCursor": "QVFIUkxxxxxxxx"
}

mediaType is Instagram’s own label (REELS, FEED, STORY, or the format - IMAGE, VIDEO, CAROUSEL_ALBUM); for Facebook it is always POST. nextCursor is null on the last page.

If nothing can be listed the call still returns 200 with connected: false and an empty posts array, plus a reason telling you why:

reason What to do
(absent) No page is connected yet - run the connect flow first.
no_instagram_account A Facebook Page is connected but no Instagram business account is linked to it. Facebook posts still list fine.
token_expired The stored page credential no longer works - reconnect the channel.

Disconnect Instagram + Messenger

DELETE /channels/meta
curl -X DELETE "https://api.youraiconnector.com/v1/channels/meta" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{ "success": true, "disconnected": true }

This stops inbound routing for both Instagram and Messenger. It is idempotent - calling it when nothing is connected still succeeds.


WhatsApp Business

This connects an official WhatsApp Business number. The number must already exist on the account before you call connect. Like Meta, the account holder authorizes in their browser, then you poll until the number reports ONLINE.

Step 1 - Start the WhatsApp Business connection

POST /channels/whatsapp/connect

cURL

curl -X POST "https://api.youraiconnector.com/v1/channels/whatsapp/connect?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "phone_number": "+14155551234" }'

JavaScript

const res = await fetch("https://api.youraiconnector.com/v1/channels/whatsapp/connect", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ phone_number: "+14155551234" }),
});
const data = await res.json();
// Open data.oauth_url in the account holder's browser.

Python

res = requests.post(
    "https://api.youraiconnector.com/v1/channels/whatsapp/connect",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"phone_number": "+14155551234"},
)
data = res.json()
# Open data["oauth_url"] in the account holder's browser.
Field Required Description
phone_number Yes The number to connect, in E.164 format (e.g. +14155551234).
only_waba_sharing No Restrict the authorization to sharing an existing WhatsApp Business Account, skipping new sender setup. Defaults to false.
retry No Re-run authorization for a number whose previous attempt did not complete. Defaults to false.
business_name No Cosmetic override for the business name shown on the consent screen only (max 256 chars). Not stored.
description No Cosmetic override for the business description shown on the consent screen only (max 256 chars). Not stored.

Response

{
  "success": true,
  "status": "pending",
  "oauth_url": "https://www.facebook.com/v21.0/dialog/oauth?client_id=...&state=...",
  "state_token": "opaque-one-time-token",
  "expires_at": "2026-06-10T12:30:00.000Z"
}

Open oauth_url in the account holder’s browser to authorize. Once they approve, registration completes in the background.

Step 2 - Poll the status until ONLINE

GET /channels/whatsapp/connect/{phoneNumber}/status

Poll this until status is ONLINE.

cURL

curl "https://api.youraiconnector.com/v1/channels/whatsapp/connect/+14155551234/status" \
  -H "X-API-Key: YOUR_API_KEY"

JavaScript

const phone = encodeURIComponent("+14155551234");
const res = await fetch(
  `https://api.youraiconnector.com/v1/channels/whatsapp/connect/${phone}/status`,
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
// Poll until data.status === "ONLINE".

Python

import urllib.parse

phone = urllib.parse.quote("+14155551234")
res = requests.get(
    f"https://api.youraiconnector.com/v1/channels/whatsapp/connect/{phone}/status",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
# Poll until data["status"] == "ONLINE".

Response

{
  "success": true,
  "phone_number": "+14155551234",
  "channel": "whatsapp",
  "status": "ONLINE",
  "status_reason": null,
  "live": true
}

The status field can be:

status Meaning
PENDING Authorized, approval still in progress. Keep polling.
ONLINE Connected and ready to send.
RATE_LIMITED Too many attempts - wait before retrying.
REGISTRATION_FAILED Setup could not be completed.
DELETED The registration no longer exists.

live: true means the status was checked against the provider in real time; false means it came from the last cached state.

Disconnect a WhatsApp Business number

DELETE /channels/whatsapp/{phoneNumber}
curl -X DELETE "https://api.youraiconnector.com/v1/channels/whatsapp/+14155551234" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{ "success": true, "phone_number": "+14155551234", "disconnected": true }

The number itself stays on the account, so you can reconnect it later.


WhatsApp Web

WhatsApp Web links a regular WhatsApp number by scanning a QR code, just like linking a device in the WhatsApp app. The flow is: start the session, fetch the QR code and show it, then poll until the status is connected.

Step 1 - Start a WhatsApp Web pairing session

POST /channels/whatsapp-web/connections

cURL

curl -X POST "https://api.youraiconnector.com/v1/channels/whatsapp-web/connections?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "phone_number": "+15551230000" }'

JavaScript

const res = await fetch("https://api.youraiconnector.com/v1/channels/whatsapp-web/connections", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ phone_number: "+15551230000" }),
});
const data = await res.json();

Python

res = requests.post(
    "https://api.youraiconnector.com/v1/channels/whatsapp-web/connections",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"phone_number": "+15551230000"},
)
data = res.json()
Field Required Description
phone_number Yes The WhatsApp number to connect, in E.164 format.
proxy_country No ISO 3166-1 alpha-2 country code for the routing region. Auto-detected from the number when omitted.
force_new No Discard any existing session and start fresh pairing. Defaults to false.
import_contacts No Import the device’s existing contacts on first connection. Defaults to false.
pause_ai_for_imported_contacts No When importing contacts, keep automated replies paused for them. Defaults to true.
import_existing_chats No Import existing chat history (requires import_contacts: true). Defaults to false.

Response

{
  "success": true,
  "phone_number": "+15551230000",
  "session_id": "session-id",
  "status": "qr_pending",
  "connect_url": "https://api.youraiconnector.com/v1/channels/whatsapp-web/connect?token=eyJhbGciOi...",
  "connect_url_expires_at": 1717000000000,
  "poll_qr_path": "/v1/channels/whatsapp-web/connections/%2B15551230000/qr",
  "poll_status_path": "/v1/channels/whatsapp-web/connections/%2B15551230000/status"
}

Easiest option for WhatsApp Web: hand over connect_url

The response includes a ready-made connect_url: a hosted page that shows the QR code, refreshes it automatically as it rotates, and switches to a success message the moment the number is linked. Just give this link to the account holder (open it in a browser, send it to them, or show it as a QR/button) and have them scan it with WhatsApp - you don’t need to fetch the QR or poll anything yourself. The link works for about 30 minutes (connect_url_expires_at); if it lapses before they finish, start a new connection to get a fresh one.

This is the recommended path when a person can open a link. The manual steps below (fetch the QR yourself, poll the status) are for integrations that want to render the QR inside their own interface instead.

The response also hands you the exact poll_qr_path and poll_status_path to use, so you don’t have to build them yourself.

Step 2 - Fetch the QR code and show it

GET /channels/whatsapp-web/connections/{phoneNumber}/qr

cURL

curl "https://api.youraiconnector.com/v1/channels/whatsapp-web/connections/+15551230000/qr" \
  -H "X-API-Key: YOUR_API_KEY"

JavaScript

const phone = encodeURIComponent("+15551230000");
const res = await fetch(
  `https://api.youraiconnector.com/v1/channels/whatsapp-web/connections/${phone}/qr`,
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
// Render data.qr_data_url as an <img src> for the user to scan.

Python

import urllib.parse

phone = urllib.parse.quote("+15551230000")
res = requests.get(
    f"https://api.youraiconnector.com/v1/channels/whatsapp-web/connections/{phone}/qr",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
# Render data["qr_data_url"] for the user to scan.

Response

{
  "success": true,
  "phone_number": "+15551230000",
  "status": "qr_pending",
  "qr_code": "2@raw-qr-payload-string...",
  "qr_data_url": "data:image/png;base64,iVBORw0KGgo...",
  "expires_at": "2026-06-10T12:05:00.000Z"
}

Render the QR for the user to scan with their phone (WhatsApp > Linked Devices > Link a Device):

  • qr_data_url is a ready-to-use image - drop it straight into an <img src>.
  • qr_code is the raw payload if you’d rather generate the image yourself.

The QR is short-lived. If you call this right after starting the session you may get a 404 with “QR code not available yet” - just wait a moment and retry. If you get a 410 (“QR code expired”), start the connection over to get a fresh code.

Step 3 - Poll the status until connected

GET /channels/whatsapp-web/connections/{phoneNumber}/status

cURL

curl "https://api.youraiconnector.com/v1/channels/whatsapp-web/connections/+15551230000/status" \
  -H "X-API-Key: YOUR_API_KEY"

JavaScript

const phone = encodeURIComponent("+15551230000");
const res = await fetch(
  `https://api.youraiconnector.com/v1/channels/whatsapp-web/connections/${phone}/status`,
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
// Poll until data.status === "connected" (or "open").

Python

import urllib.parse

phone = urllib.parse.quote("+15551230000")
res = requests.get(
    f"https://api.youraiconnector.com/v1/channels/whatsapp-web/connections/{phone}/status",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
# Poll until data["status"] == "connected" (or "open").

Response

{
  "success": true,
  "phone_number": "+15551230000",
  "status": "connected",
  "has_qr": false,
  "qr_expires_at": null,
  "last_activity": null,
  "message_count": null,
  "proxy": null,
  "live": true
}
status Meaning
not_initialized No session yet (terminal failure).
qr_pending Waiting for the QR to be scanned.
connecting Scanned, finishing setup.
connected / open Linked and live - this is success.
disconnected Session ended (terminal failure).

Disconnect a WhatsApp Web session

DELETE /channels/whatsapp-web/connections/{phoneNumber}
curl -X DELETE "https://api.youraiconnector.com/v1/channels/whatsapp-web/connections/+15551230000" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{ "success": true, "phone_number": "+15551230000", "status": "removed" }

This unlinks the device and removes the connection. It always cleans up local state, so it is idempotent even if the underlying session was already gone.


Telegram

Availability: Telegram connects like any other channel and is open to every account — you do not need it switched on for you. The Telegram endpoints below can still return 403 if Telegram is not included in the account’s plan, in which case the error reads "This channel is not included in your current plan. Upgrade to unlock it.".

Telegram connects a personal account by phone number plus a one-time login code (and a two-factor password, if the account has one set). The flow is: start the session, submit the code, optionally submit the password, then confirm via status.

Step 1 - Start a Telegram connection session

POST /channels/telegram/connect

cURL

curl -X POST "https://api.youraiconnector.com/v1/channels/telegram/connect?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "phone_number": "+14155550100" }'

JavaScript

const res = await fetch("https://api.youraiconnector.com/v1/channels/telegram/connect", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ phone_number: "+14155550100" }),
});
const data = await res.json();

Python

res = requests.post(
    "https://api.youraiconnector.com/v1/channels/telegram/connect",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"phone_number": "+14155550100"},
)
data = res.json()
Field Required Description
phone_number Yes The account phone number to connect, in E.164 format.
mode No code (default) sends a one-time login code to the account; qr returns a login token and QR URL to display.
proxy_country No ISO 3166-1 alpha-2 country code for the outbound network route.
force_new No When true, discards any existing session and starts fresh.

Response

{
  "success": true,
  "phone_number": "+14155550100",
  "status": "code_required",
  "session_id": "session-id",
  "connect_url": "https://api.youraiconnector.com/v1/channels/telegram/connect/page?token=eyJhbGciOi...",
  "connect_url_expires_at": 1717000000000
}

In code mode the account receives a login code in Telegram and status is code_required. (In qr mode the response also includes login_token and qr_url to display for scanning, and status is qr_required.)

Easiest option for Telegram: hand over connect_url

The response includes a ready-made connect_url: a hosted page that finishes the connection on its own. In code mode the account holder enters the login code - and a two-step verification password if their account has one. In qr mode the page shows a QR that refreshes itself for them to scan from the Telegram app. Either way it reports success on its own, so you can just give this link to the account holder instead of building your own UI and polling. The link works for about 30 minutes (connect_url_expires_at); if it lapses, start a new connection to get a fresh one.

The manual steps below (collect the code yourself, submit it, poll the status; or render qr_url and poll) are for integrations that want to render the UI themselves.

Step 2 - Submit the login code

POST /channels/telegram/connect/{phoneNumber}/verify-code

cURL

curl -X POST "https://api.youraiconnector.com/v1/channels/telegram/connect/+14155550100/verify-code" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "code": "12345" }'

JavaScript

const phone = encodeURIComponent("+14155550100");
const res = await fetch(
  `https://api.youraiconnector.com/v1/channels/telegram/connect/${phone}/verify-code`,
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ code: "12345" }),
  }
);
const data = await res.json();

Python

import urllib.parse

phone = urllib.parse.quote("+14155550100")
res = requests.post(
    f"https://api.youraiconnector.com/v1/channels/telegram/connect/{phone}/verify-code",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"code": "12345"},
)
data = res.json()

Response

{
  "success": true,
  "phone_number": "+14155550100",
  "status": "connected",
  "telegram_user_id": "100000001",
  "username": "myhandle"
}

If status is connected, you’re done. If the account has two-factor enabled, status will be password_required instead - go to step 3.

Step 3 - Submit the two-factor password (only if needed)

POST /channels/telegram/connect/{phoneNumber}/verify-password

Only call this when step 2 returned password_required.

cURL

curl -X POST "https://api.youraiconnector.com/v1/channels/telegram/connect/+14155550100/verify-password" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "password": "the-2fa-password" }'

JavaScript

const phone = encodeURIComponent("+14155550100");
const res = await fetch(
  `https://api.youraiconnector.com/v1/channels/telegram/connect/${phone}/verify-password`,
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ password: "the-2fa-password" }),
  }
);
const data = await res.json();

Python

import urllib.parse

phone = urllib.parse.quote("+14155550100")
res = requests.post(
    f"https://api.youraiconnector.com/v1/channels/telegram/connect/{phone}/verify-password",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"password": "the-2fa-password"},
)
data = res.json()

Response

{
  "success": true,
  "phone_number": "+14155550100",
  "status": "connected",
  "telegram_user_id": "100000001",
  "username": "myhandle"
}

Check Telegram status

GET /channels/telegram/connect/{phoneNumber}/status
curl "https://api.youraiconnector.com/v1/channels/telegram/connect/+14155550100/status" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{
  "success": true,
  "phone_number": "+14155550100",
  "status": "connected",
  "telegram_user_id": "100000001",
  "live": true
}

status can be connected, code_required, password_required, initializing, disconnected, not_initialized, or error.

Disconnect Telegram

DELETE /channels/telegram/{phoneNumber}
curl -X DELETE "https://api.youraiconnector.com/v1/channels/telegram/+14155550100" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{ "success": true, "phone_number": "+14155550100", "status": "removed" }

Idempotent - repeated calls succeed.


Instagram (personal account)

Limited-availability beta, enabled per account. This connects a personal Instagram account by logging in with its username and password (not the official Business API). If the account is not enabled for the beta, the connect call returns a permission error.

Because this needs the account holder’s own Instagram login, the simplest path is to hand them the hosted connect_url and let them enter their credentials there - your integration never handles the password.

Step 1 - Start an Instagram (personal) connection

POST /channels/instagram-private/connect

Send the Instagram username and password.

Response

{
  "success": true,
  "status": "connected",
  "connect_url": "https://api.youraiconnector.com/v1/channels/instagram-private/connect/page?token=eyJhbGciOi...",
  "connect_url_expires_at": 1717000000000
}

If the account has two-factor authentication or Instagram presents a checkpoint, status comes back as two_factor_required or challenge_required - submit the code to /connect/{id}/verify-2fa or /connect/{id}/verify-challenge below, then poll /connect/{id}/status until connected. {id} is the normalized Instagram username returned as account_id/username in the response above - use it on every step below.

Step 2 - Submit the two-factor code (if asked for)

POST /channels/instagram-private/connect/{id}/verify-2fa

Only call this when step 1 (or step 3) returned two_factor_required.

curl -X POST "https://api.youraiconnector.com/v1/channels/instagram-private/connect/yourbrand/verify-2fa" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "code": "123456" }'

Response

{
  "success": true,
  "account_id": "yourbrand",
  "status": "connected",
  "ig_user_id": "17890000000000000",
  "username": "yourbrand"
}

status can come back connected (done), two_factor_required (wrong code, try again), or challenge_required (Instagram also wants a checkpoint code - go to step 3).

Step 3 - Submit the checkpoint confirmation code (if asked for)

POST /channels/instagram-private/connect/{id}/verify-challenge

Only call this when a previous step returned challenge_required. Same request and response shape as step 2 above.

curl -X POST "https://api.youraiconnector.com/v1/channels/instagram-private/connect/yourbrand/verify-challenge" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "code": "123456" }'

Check Instagram (personal) status

GET /channels/instagram-private/connect/{id}/status

Poll this until status is connected, or until it reports a terminal failure.

curl "https://api.youraiconnector.com/v1/channels/instagram-private/connect/yourbrand/status" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{
  "success": true,
  "account_id": "yourbrand",
  "status": "connected",
  "ig_user_id": "17890000000000000",
  "username": "yourbrand",
  "live": true
}

status can be connected, two_factor_required, challenge_required, initializing, disconnected, not_initialized, or error. live: true means this was read live from the connection worker rather than a cached value.

Easiest option for Instagram (personal): hand over connect_url

The response includes a connect_url: a hosted page where the account holder enters their Instagram username and password (and a 2FA or checkpoint code if Instagram asks for one), and which reports success on its own. The credentials go straight to Instagram and are not stored. Give this link to the account holder instead of collecting their password in your own UI. The link works for about 30 minutes (connect_url_expires_at).

Disconnect Instagram (personal)

DELETE /channels/instagram-private/{id}

Idempotent - repeated calls succeed.

Sync followers

POST /channels/instagram-private/{id}/sync-followers

Manually triggers a follower sync for a connected account - the same job that runs automatically in the background, exposed here for an on-demand “Refresh followers” action. It fetches the account’s current follower list, records anyone new, and (when a Live campaign has follower outreach turned on) sends new followers an opening DM, up to a daily cap.

curl -X POST "https://api.youraiconnector.com/v1/channels/instagram-private/yourbrand/sync-followers" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{
  "success": true,
  "accountId": "yourbrand",
  "totalFollowers": 1204,
  "newFollowers": 6,
  "dmsSent": 6,
  "isBaselineSeed": false
}

These five fields are the one place on this page that come back camelCase instead of snake_case - that’s how this endpoint is wired today, not a typo. isBaselineSeed: true means this was the very first sync after connecting, which only records the starting follower list and never sends outreach DMs (so dmsSent is always 0 on that run).

The very first call for an account can take a while (walking the full follower list); later calls are faster since only new followers get diffed. 404 means the account isn’t connected; 412 means the connection hasn’t finished initializing yet - wait and retry.


LINE

LINE is the simplest channel to connect because there is no browser redirect or polling. The customer creates a Messaging API channel in the LINE Developers console, copies two values, and you submit them in a single call. You then give them back a webhook URL to paste into the console.

Step 1 - Connect with the channel credentials

POST /channels/line

cURL

curl -X POST "https://api.youraiconnector.com/v1/channels/line?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_access_token": "LONG_LIVED_CHANNEL_ACCESS_TOKEN",
    "channel_secret": "CHANNEL_SECRET"
  }'

JavaScript

const res = await fetch("https://api.youraiconnector.com/v1/channels/line", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    channel_access_token: "LONG_LIVED_CHANNEL_ACCESS_TOKEN",
    channel_secret: "CHANNEL_SECRET",
  }),
});
const data = await res.json();

Python

res = requests.post(
    "https://api.youraiconnector.com/v1/channels/line",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "channel_access_token": "LONG_LIVED_CHANNEL_ACCESS_TOKEN",
        "channel_secret": "CHANNEL_SECRET",
    },
)
data = res.json()
Field Required Description
channel_access_token Yes The Official Account’s long-lived Messaging API channel access token. Used to send and receive messages.
channel_secret Yes The Messaging API channel secret, used to verify inbound event signatures.
channel_id No The numeric channel id. Informational only.

Response

{
  "success": true,
  "status": "connected",
  "bot_user_id": "Uxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "basic_id": "@mybusiness",
  "display_name": "My Business",
  "picture_url": "https://...",
  "chat_mode": "bot",
  "chat_mode_ok": true,
  "webhook_url": "https://api.youraiconnector.com/line/webhook/..."
}

Two fields matter for what you do next:

  • webhook_url - the customer must paste this into the Webhook URL field of their LINE channel in the LINE Developers console (and enable “Use webhook”). Until they do, no inbound messages arrive. Show this to them prominently.
  • chat_mode_ok - when false, the Official Account is in “chat” mode and will not receive or send messages until it is switched to “bot” mode in the LINE Official Account Manager. Gate your onboarding on this flag and tell the customer to switch the mode.

The channel_access_token and channel_secret are never returned by any endpoint. Store them on your side if you need them again; otherwise re-paste from the LINE console.

The bot_user_id returned here is the connection identifier you use in the status, verify, and disconnect calls below.

Step 2 - Re-verify after webhook setup

POST /channels/line/{botUserId}/verify-webhook

After the customer finishes configuring the webhook URL and switches to bot mode, call this to re-validate the stored token and refresh the cached chat mode.

curl -X POST "https://api.youraiconnector.com/v1/channels/line/Uxxxxxxxx.../verify-webhook" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{
  "success": true,
  "token_valid": true,
  "chat_mode": "bot",
  "chat_mode_ok": true,
  "webhook_url": "https://api.youraiconnector.com/line/webhook/..."
}

If token_valid is false, the stored access token no longer authenticates - have the customer reissue it in the console and call POST /channels/line again with the new token.

Check LINE status

GET /channels/line/{botUserId}/status
curl "https://api.youraiconnector.com/v1/channels/line/Uxxxxxxxx.../status" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{
  "success": true,
  "bot_user_id": "Uxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "channel": "line",
  "status": "connected",
  "basic_id": "@mybusiness",
  "display_name": "My Business",
  "picture_url": "https://...",
  "chat_mode": "bot",
  "is_active": true,
  "live": false
}

LINE has no live status feed, so live is always false here - the values reflect the state captured at connect (or last verify) time.

Disconnect LINE

DELETE /channels/line/{botUserId}
curl -X DELETE "https://api.youraiconnector.com/v1/channels/line/Uxxxxxxxx..." \
  -H "X-API-Key: YOUR_API_KEY"

Response

{ "success": true, "status": "removed", "bot_user_id": "Uxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }

Viber

Viber connects the same way LINE does - paste the bot’s auth token from the Viber Admin Panel in one call - with one difference worth knowing: connecting also REGISTERS our webhook on your bot right then, so there’s no separate console step afterward. That also means a connect attempt can fail if our ingress can’t answer Viber’s synchronous webhook check, not only if the token itself is wrong.

Step 1 - Connect with the bot’s auth token

POST /channels/viber
Field Required Description
auth_token Yes The bot’s auth token, from the Viber Admin Panel (My Bot Settings).
curl -X POST "https://api.youraiconnector.com/v1/channels/viber?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "auth_token": "444d5555e6666f7777a8888b9999c000" }'

Response

{
  "success": true,
  "status": "connected",
  "bot_id": "botIdFromViber",
  "bot_name": "My Business Bot",
  "bot_avatar": "https://...",
  "bot_uri": "mybusinessbot",
  "subscribers_count": 0,
  "webhook_url": "https://api.youraiconnector.com/v1/incoming-viber-message/...",
  "event_types": ["delivered", "seen", "failed", "subscribed", "unsubscribed", "conversation_started"]
}

The auth token is never echoed back by any endpoint - store it on your side if you’ll need to re-paste it. bot_id is the connection identifier used by the status, verify, and disconnect calls below.

Check Viber status

GET /channels/viber/{botId}/status

Reports the stored connection state. Add ?live=true to also re-check the bot against Viber and refresh the cached webhook registration - useful before assuming a silent bot is actually broken.

curl "https://api.youraiconnector.com/v1/channels/viber/botIdFromViber/status?live=true" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{
  "success": true,
  "bot_id": "botIdFromViber",
  "channel": "viber",
  "status": "connected",
  "bot_name": "My Business Bot",
  "bot_avatar": "https://...",
  "bot_uri": "mybusinessbot",
  "webhook_url": "https://api.youraiconnector.com/v1/incoming-viber-message/...",
  "registered_webhook": "https://api.youraiconnector.com/v1/incoming-viber-message/...",
  "webhook_ok": true,
  "subscribers_count": 128,
  "is_active": true,
  "live": true
}

webhook_ok: false means the bot’s webhook no longer points at us - inbound messages are dead. This usually means another tool connected the same bot afterward (Viber’s webhook registration is last-write-wins). Fix it with the re-verify call below, no need to ask the customer to re-paste their token. live is false when the response is the last cached state rather than a fresh check against Viber.

Re-register the webhook

POST /channels/viber/{botId}/verify-webhook

The repair action for webhook_ok: false - re-registers our webhook on the bot using the already-stored auth token.

curl -X POST "https://api.youraiconnector.com/v1/channels/viber/botIdFromViber/verify-webhook" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{ "success": true, "token_valid": true, "webhook_ok": true, "webhook_url": "https://api.youraiconnector.com/v1/incoming-viber-message/...", "event_types": ["delivered", "seen", "failed", "subscribed", "unsubscribed", "conversation_started"] }

token_valid: false means the stored token no longer works - reconnect with POST /channels/viber and a fresh token.

Disconnect Viber

DELETE /channels/viber/{botId}

Unregisters our webhook on Viber’s side (best-effort) and removes the connection.

curl -X DELETE "https://api.youraiconnector.com/v1/channels/viber/botIdFromViber" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{ "success": true, "status": "removed", "bot_id": "botIdFromViber", "webhook_removed": true }

TikTok

Availability: Limited-availability beta, enabled per account. Connecting TikTok returns a permission error until the account is enabled for it.

TikTok Business Messaging is a full OAuth channel like Meta, but simpler on the polling side: there’s no dedicated status-polling step to build against, because the connected account shows up on its own once TikTok redirects back and the connection is written. The status endpoint below exists for confirming state on demand (support tooling, health checks), not as something you need to loop on during connect.

Step 1 - Start the TikTok connection

POST /channels/tiktok/connect

Takes no credentials - the account holder authorizes entirely in their browser.

curl -X POST "https://api.youraiconnector.com/v1/channels/tiktok/connect?apiKey=YOUR_API_KEY"

Response

{
  "success": true,
  "status": "pending_authorization",
  "oauth_url": "https://www.tiktok.com/v2/auth/authorize?client_key=...&state=...",
  "state_token": "opaque-one-time-token",
  "expires_at": "2026-06-10T12:30:00.000Z"
}

Open oauth_url in the account holder’s browser so they can log in to TikTok and approve access. The state expires at expires_at (about 30 minutes) - if it lapses, start over. There is no connect_url hosted-page shortcut for TikTok; opening oauth_url yourself is the only path.

Check TikTok status

GET /channels/tiktok/{openId}/status

openId is the TikTok Business Account’s open_id, known once the OAuth callback has run.

curl "https://api.youraiconnector.com/v1/channels/tiktok/openIdFromTikTok/status" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{
  "success": true,
  "open_id": "openIdFromTikTok",
  "channel": "tiktok",
  "status": "connected",
  "business_id": "openIdFromTikTok",
  "username": "mybusiness",
  "display_name": "My Business",
  "avatar_url": "https://...",
  "status_reason": null,
  "is_active": true,
  "live": false
}

TikTok has no cheap live health check, so live is always false here - the fields reflect what connect (or the last token refresh) wrote. status: "reauth_required" with status_reason set means the account needs to go through connect again; TikTok tokens are refreshed automatically on a yearly rotation, and this is what shows up if that rotation ever fails.

Disconnect TikTok

DELETE /channels/tiktok/{openId}
curl -X DELETE "https://api.youraiconnector.com/v1/channels/tiktok/openIdFromTikTok" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{ "success": true, "status": "removed", "open_id": "openIdFromTikTok" }

GoHighLevel

GoHighLevel (GHL) is a CRM integration, not a messaging channel - connecting it does not use up a channel slot on the plan, because it rides the account’s existing channels rather than adding a new one. It’s also the one integration on this page that can hold more than one connection at once: each GHL sub-account (“location”) the customer installs the app on gets its own entry.

Step 1 - Start the GHL connection

POST /channels/ghl/connect
Field Required Description
brand No Which GHL marketplace listing to authorize through. Defaults to the standard listing - only relevant if your deployment has more than one marketplace app configured.
curl -X POST "https://api.youraiconnector.com/v1/channels/ghl/connect?apiKey=YOUR_API_KEY"

Response

{
  "success": true,
  "status": "pending_authorization",
  "oauth_url": "https://marketplace.gohighlevel.com/oauth/chooselocation?client_id=...&state=...",
  "state_token": "opaque-one-time-token",
  "brand": "dmchamp",
  "expires_at": "2026-06-10T12:30:00.000Z"
}

Open oauth_url in the account holder’s browser so they can pick a GHL location and approve access. The state expires at expires_at (about 30 minutes).

List GHL connections

GET /channels/ghl/status

Unlike other channels this isn’t a single connection’s status - it lists every location the account has connected.

curl "https://api.youraiconnector.com/v1/channels/ghl/status" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{
  "success": true,
  "connections": [
    {
      "location_id": "abc123location",
      "company_id": "xyz789company",
      "brand": "dmchamp",
      "status": "connected",
      "status_reason": null,
      "scopes": ["conversations.readonly", "conversations.write", "conversations/message.write"],
      "connected_at": "2026-06-01T10:00:00.000Z",
      "conversation_provider_id": "provider-id-in-ghl",
      "trigger_subscriptions": [
        { "id": "sub_1", "key": "InboundMessage", "workflow_id": "wf_123" }
      ]
    }
  ]
}

Disconnect a GHL location

DELETE /channels/ghl/{locationId}

Deletes the connection here, which stops every sync and trigger for that location. This does not uninstall the app on the GHL side - the customer removes it from their GHL marketplace installs if they want that too.

curl -X DELETE "https://api.youraiconnector.com/v1/channels/ghl/abc123location" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{ "success": true, "status": "disconnected", "location_id": "abc123location" }

Phone numbers (buy and release)

Instead of connecting an existing number, you can buy a new WhatsApp-capable number directly. Search for available numbers, buy one, then poll until it finishes provisioning.

Note: Numbers bought here are WhatsApp-capable. WhatsApp sender registration runs in the background after purchase, so you poll the status until it reaches ONLINE before sending. Credits are deducted on purchase and are not refunded when you release the number.

Step 1 - Search available numbers

GET /phone-numbers/available?country_code=ISO2

cURL

curl "https://api.youraiconnector.com/v1/phone-numbers/available?country_code=US&apiKey=YOUR_API_KEY"

JavaScript

const res = await fetch(
  "https://api.youraiconnector.com/v1/phone-numbers/available?country_code=US",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();

Python

res = requests.get(
    "https://api.youraiconnector.com/v1/phone-numbers/available",
    params={"country_code": "US"},
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
Query parameter Required Description
country_code Yes ISO 3166-1 alpha-2 country code to search in (e.g. US, GB, NL).
type No Preferred number class, local or mobile. Both classes may still be returned.

Response

{
  "success": true,
  "phone_numbers": [
    {
      "phone_number": "+14155551234",
      "purchase_credits": 50,
      "monthly_credits": 50,
      "cost_usd": 1.15
    }
  ]
}

Each result shows the one-time purchase_credits and the recurring monthly_credits. A platform-supplied number costs at least 50 credits a month, rising with the carrier’s own monthly price, charged at purchase and on every renewal. Quote the purchase_credits / monthly_credits the search returns; never derive a price yourself. The first search on a new account provisions some underlying resources, so it may be a little slower than later searches.

Step 2 - Buy a number

POST /phone-numbers

Use a phone_number from the search results.

cURL

curl -X POST "https://api.youraiconnector.com/v1/phone-numbers" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+14155551234",
    "country_code": "US",
    "display_name": "Support line"
  }'

JavaScript

const res = await fetch("https://api.youraiconnector.com/v1/phone-numbers", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    phone_number: "+14155551234",
    country_code: "US",
    display_name: "Support line",
  }),
});
const data = await res.json();

Python

res = requests.post(
    "https://api.youraiconnector.com/v1/phone-numbers",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "phone_number": "+14155551234",
        "country_code": "US",
        "display_name": "Support line",
    },
)
data = res.json()
Field Required Description
phone_number Yes A number returned by the available-numbers search, in E.164 format.
country_code Yes ISO 3166-1 alpha-2 country code (e.g. US).
display_name No A friendly label. Defaults to the phone number.
category No Optional category label.

Response

{
  "success": true,
  "phone_number": "+14155551234",
  "channel": "whatsapp",
  "whatsapp_status": "PURCHASED",
  "outgoing_status": "PURCHASED",
  "status": "PURCHASED",
  "purchase_credits": 50,
  "monthly_credits": 50
}

The number starts in the PURCHASED state. WhatsApp registration then proceeds in the background: PURCHASED -> PENDING -> ONLINE.

If purchase fails because a business address is missing or another required detail isn’t set, you’ll get a 400 with a descriptive error. Set up the missing detail and try again.

Step 3 - Poll until ONLINE

GET /phone-numbers/{phoneNumber}/status

This is the shared phone-number status endpoint - it works for bought WhatsApp numbers as well as your other connected numbers.

cURL

curl "https://api.youraiconnector.com/v1/phone-numbers/+14155551234/status" \
  -H "X-API-Key: YOUR_API_KEY"

JavaScript

const phone = encodeURIComponent("+14155551234");
const res = await fetch(
  `https://api.youraiconnector.com/v1/phone-numbers/${phone}/status`,
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
// Poll until data.status === "ONLINE".

Python

import urllib.parse

phone = urllib.parse.quote("+14155551234")
res = requests.get(
    f"https://api.youraiconnector.com/v1/phone-numbers/{phone}/status",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()
# Poll until data["status"] == "ONLINE".

Response

{
  "success": true,
  "phone_number": "+14155551234",
  "channel": "whatsapp",
  "status": "ONLINE",
  "status_reason": null,
  "live": true
}

Step 4 - Release a number

DELETE /phone-numbers/{phoneNumber}
curl -X DELETE "https://api.youraiconnector.com/v1/phone-numbers/+14155551234" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{ "success": true, "phone_number": "+14155551234", "released": true }

What this does depends on whose number it is.

For a number rented through the platform, it is a true release: the WhatsApp sender is de-registered, the number is handed back to the carrier and removed from the account, a 7-day cooldown is applied during which the number cannot be repurchased by anyone, and no credits are refunded.

For a number the account brought itself (its own Twilio account, its own Meta app or WhatsApp Business Account, or an Android SMS gateway), the same call only removes it from the account. Nothing is released at the upstream provider and no cooldown is written, so the number can be reconnected immediately. Its WhatsApp sender registration, if it had one, may or may not survive: the teardown tries to delete the sender using the account’s platform-managed Twilio credentials. On an account still on the managed setup those credentials are valid and the sender is deleted, so reconnecting means registering it again. On an account that has switched to its own Twilio, the delete cannot authenticate, and the sender is left registered in that account — reconnecting is then just re-attaching the existing sender.

Add a number you already own (BYO)

POST /phone-numbers/byo

Skips the search-and-buy flow above entirely. Use this when the account brings its own number (their own Twilio, their own Meta WhatsApp Business Account, or an Android SMS gateway) instead of renting one through the platform. This only records the number - no credits are charged, and nothing is provisioned with a provider here. The number stays inactive until the account holder completes WhatsApp OAuth to register a Sender on it (the same flow the dashboard’s “Bring your own number” button starts).

Field Required Description
phone_number Yes The number to add, in E.164 format (e.g. +14155551234).
country_code Yes ISO 3166-1 alpha-2 country code (e.g. US).
display_name No A friendly label. Defaults to the phone number.
category No Optional category label.
curl -X POST "https://api.youraiconnector.com/v1/phone-numbers/byo?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+14155551234",
    "country_code": "US",
    "display_name": "Support line"
  }'

Response (201 Created):

{
  "success": true,
  "phone_number": "+14155551234",
  "channel": "whatsapp",
  "type": "BYO",
  "whatsapp_status": "ADDED",
  "outgoing_status": "ADDED",
  "is_active": false
}

A phone_number that isn’t a real E.164 number (or that looks like Meta’s WhatsApp test number, which can never message real customers) returns 400. Adding a number that already exists on the account - even spelled slightly differently, like Mexico’s +52 vs +521 forms - returns 409 rather than creating a duplicate row.

Set a number as primary

POST /phone-numbers/{phoneNumber}/set-primary

Flips one number to is_active: true and every other number on the account to is_active: false, atomically - the account never ends up with two active numbers, or none, mid-request. is_active can’t be set through the general update endpoint on purpose; this dedicated call is the only way to change which number is primary.

curl -X POST "https://api.youraiconnector.com/v1/phone-numbers/+14155551234/set-primary" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{
  "success": true,
  "phone_number": {
    "id": "+14155551234",
    "phone_number": "+14155551234",
    "display_name": "Support line",
    "channel": "whatsapp",
    "is_active": true,
    "whatsapp_status": "ONLINE"
  }
}

phone_number here is the full number object (the same shape GET /phone-numbers returns), not just the string. A phoneNumber not on the account returns 404.

Remove a number’s record (without releasing it)

DELETE /phone-numbers/{phoneNumber}/record

A plain delete of the number’s record on this account - no provider-side release or de-registration, and no 7-day cooldown like the release step above applies. Use this to clear out BYO, WhatsApp Web, Telegram, or LINE records, or a stale entry, without going through the managed release flow. Unlike a release, deleting a number that isn’t on the account is a 404, not a silent success.

curl -X DELETE "https://api.youraiconnector.com/v1/phone-numbers/+14155551234/record" \
  -H "X-API-Key: YOUR_API_KEY"

Response

{ "success": true, "phone_number": "+14155551234", "deleted": true }

Route a channel to a campaign

Connecting a channel gets messages into the account. It does not decide which AI Agent answers them.

Routing is handled by Entry Points on an AI Agent, not by campaigns. Each channel has one channel-default Entry Point naming the Agent that answers new, unknown contacts on that channel:

What you want to do Call
Point a channel at the Agent that should answer it PUT /entry-points/channel-defaults with body { "channel": "instagram", "agent_id": "AGENT_ID" }
Check whether the Entry Points ladder is live for the account GET /entry-points/routing-status, which returns { "success": true, "cutover_enabled": true } once Entry Points decide that account’s routing
Leave a channel with no Agent answering it DELETE /entry-points/channel-defaults?channel=instagram

Until a channel has an Entry Point, a first message from someone you have never spoken to is still stored, but nothing picks it up and no assistant replies. This is the step most integrations miss: connecting Instagram and creating an Agent is not enough on its own — you also have to point the channel at the Agent. The full set of calls — including one Agent per WhatsApp number, keyword and comment rules — is in the Entry Points API.

POST /channels/campaign still writes the legacy per-channel campaign routing map, documented below, but that map is no longer consulted for inbound routing on any account; it is retained for rollback only. Do not build against it.

Route one or more channels (legacy campaign routing map)

POST /channels/campaign

Request fields

Field Required Description
campaign_id Yes The campaign that should answer new contacts on these channels. Must belong to the account.
channels Yes A non-empty array of channels to route. Allowed: whatsapp, whatsapp_web, telegram, instagram, messenger, chat_widget, custom_channel, sms, email.

The routing slot and the campaign’s enabled_channels list are updated together in one atomic operation, so they can never drift apart. A channel already routed to a different campaign is simply re-pointed at this one.

cURL

curl -X POST "https://api.youraiconnector.com/v1/channels/campaign?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
    "channels": ["instagram", "messenger"]
  }'

JavaScript

const res = await fetch("https://api.youraiconnector.com/v1/channels/campaign", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    campaign_id: "NBCXrhqGPSFsd6MV7pRo",
    channels: ["instagram", "messenger"],
  }),
});
const data = await res.json();

Python

import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/channels/campaign",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
        "channels": ["instagram", "messenger"],
    },
)
data = res.json()

Response

{
  "success": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo",
  "channels": ["instagram", "messenger"]
}

What has to be true for routing to actually fire

On an account that still reads the legacy campaign routing map, routing succeeds as an API call but three things on the campaign decide whether a real inbound message gets answered. Check all three when a routed channel stays silent.

Requirement What happens otherwise
type is Incoming from Unknown Contacts or Combined The request is rejected with 400. Outgoing and Keywords campaigns cannot hold a routing slot.
status is Live The routing is stored but never picks anything up. A Draft campaign is the most common cause of “I routed it and nothing happens”.
ai_mode is true The contact is created and the message stored, but the assistant never replies.

Keyword matching now lives on Entry Points — create an Entry Point of type keyword on the AI Agent that should answer.

One campaign per channel

Each channel holds exactly one legacy routing slot. Routing a second campaign to the same channel silently re-points the slot and returns 200 — there is no conflict error. The previous campaign keeps handling the contacts it already has; it just stops receiving new ones.

Clear a channel’s routing

DELETE /channels/campaign/{channel}

Removes the routing for a single channel, whatever campaign it currently points at, and takes the channel back off that campaign’s enabled_channels. New unknown contacts on the channel are no longer picked up by any campaign. Contacts already in the campaign carry on as before.

curl -X DELETE "https://api.youraiconnector.com/v1/channels/campaign/instagram?apiKey=YOUR_API_KEY"

Response

{
  "success": true,
  "channel": "instagram",
  "cleared": true,
  "campaign_id": "NBCXrhqGPSFsd6MV7pRo"
}

It is idempotent: clearing a channel that was never routed also returns 200, with cleared: false and campaign_id: null. This endpoint requires the incoming campaigns feature on the plan; without it you get a 403.


Use your own Meta app (Instagram + Messenger)

By default the Instagram + Messenger connection runs through the platform’s Meta app, so that app’s name is what the account holder sees on the Facebook consent screen. If you want the consent screen to show your brand instead, you can register your own Meta app and route the whole flow through it. Once configured, it applies to your account — nothing changes in the connect calls above except the branding.

This only covers Instagram + Messenger. WhatsApp, WhatsApp Web, Telegram and LINE connections are unaffected by a custom Meta app.

What your app needs first

This is the part that takes time, and it happens entirely on the Meta side:

  1. An app of type Business, with the Messenger and Instagram products added.
  2. Advanced Access (via Meta App Review) for: pages_show_list, pages_messaging, pages_manage_metadata, pages_read_engagement, instagram_basic, instagram_manage_messages. Without Advanced Access, only people who hold a role on your app can complete the connection — your clients’ connects will fail. App Review typically takes a few weeks and requires Business Verification.
  3. A Facebook Login for Business configuration created inside your app, granting the same permissions. Its numeric configuration ID is per-app, so you must create your own.

If your app is missing any of the required permissions, the connection fails at connect time with a clear error naming what’s missing (visible in the /status poll as byo_app_missing_permissions) — rather than appearing to work and failing on the first message.

Step 1 - Save your app

PUT /account-config/meta-app

Field Required Description
app_id Yes Your Meta App ID (Settings → Basic).
app_secret Yes Your Meta App Secret. Verified against Meta before it is stored, then encrypted. Never returned by any endpoint.
config_id Yes The numeric ID of the Facebook Login for Business configuration inside your app.

All three are required for the Facebook Login flow. If you only run the Instagram Login token-push lane described further down, you can leave them out entirely.

curl -X PUT "https://api.youraiconnector.com/v1/account-config/meta-app?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "app_id": "1234567890123456",
    "app_secret": "your-app-secret",
    "config_id": "9876543210987654"
  }'

Response

{
  "success": true,
  "app_id": "1234567890123456",
  "config_id": "9876543210987654",
  "verify_token": "1f4c…a9",
  "webhook_urls": {
    "instagram": "https://api.youraiconnector.com/v1/incoming-instagram-message/byo/YOUR_ACCOUNT_ID",
    "messenger": "https://api.youraiconnector.com/v1/incoming-messenger-message/byo/YOUR_ACCOUNT_ID"
  }
}

Step 2 - Configure your app to talk to us

In your Meta app’s dashboard:

  1. Webhooks - for both the Instagram and Messenger products, set the Callback URL to the matching webhook_urls value from the response, and the Verify token to verify_token. Subscribe to the messages, messaging_postbacks and comments fields.
  2. Valid OAuth Redirect URIs - add https://api.youraiconnector.com/v1/auth-meta-callback-handler so the consent flow can return.

GET /account-config/meta-app returns the same setup material any time; DELETE /account-config/meta-app removes the app (future connects revert to the platform app — also remove the webhook subscription inside your app).

Step 3 - Connect as usual

Nothing else changes. POST /channels/meta/connect (and the hosted connect_url page) automatically uses your app for your account; the response’s uses_byo_meta_app: true confirms which app the consent screen will show. Message sending, page selection, and disconnects work identically.

Bring your own Instagram Login app (token push)

The section above covers the Facebook Login flow, where the account connects through a Facebook Page. Meta also offers Instagram API with Instagram Login (Business Login for Instagram): the account holder authenticates on Instagram itself, no Facebook account or Page involved.

If your platform already runs its own Meta app with that product, you don’t need any OAuth flow on our side at all. Your clients authorize your app, and you push us the finished credential per account:

  1. You save your Instagram app’s credentials once (so we can verify your webhooks).
  2. Per account, you push the Instagram professional account ID + the long-lived Instagram user token your app obtained.
  3. You point your app’s Instagram messaging webhook at us. Events for accounts you never pushed are acknowledged and ignored.
  4. You own the token lifecycle: refresh tokens in your own system and push each refreshed token with the same call. We never refresh a pushed token.

What your app needs first

  • The Instagram product (“API setup with Instagram login”) added to your Meta app. That product has its own App ID and App Secret pair, separate from the Facebook App ID/Secret — find them in the product’s setup panel.
  • Advanced Access (via Meta App Review) for instagram_business_basic and instagram_business_manage_messages (add instagram_business_manage_comments if you use comment automations). Without it, only people with a role on your app can authorize it.

Step 1 - Save your Instagram app credentials

Same endpoint as above — send the Instagram pair to PUT /account-config/meta-app. The Facebook fields are not needed for this lane: send the pair on its own if Instagram Login is all you run, or together with the Facebook fields if you run both. A save always describes the whole setting, so whichever set you leave out is removed.

Field Required Description
instagram_app_id Together The Instagram product’s own numeric App ID (not the Facebook App ID).
instagram_app_secret Together The Instagram product’s own App Secret. Encrypted at rest, never returned.
curl -X PUT "https://api.youraiconnector.com/v1/account-config/meta-app?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "instagram_app_id": "1122334455667788",
    "instagram_app_secret": "your-instagram-app-secret"
  }'

Response — carries the Instagram-Login webhook URL (the instagram and messenger URLs only appear when the Facebook fields are stored too):

{
  "success": true,
  "instagram_app_id": "1122334455667788",
  "verify_token": "1f4c…a9",
  "webhook_urls": {
    "instagram_login": "https://api.youraiconnector.com/v1/incoming-instagram-login-message/byo/YOUR_ACCOUNT_ID"
  }
}

In your app’s Webhooks panel for the Instagram product, set the Callback URL to webhook_urls.instagram_login, the Verify token to verify_token, and subscribe to the messages and comments fields.

Step 2 - Push a token per account

PUT /channels/instagram-login/token

Works with sub_account_id like every other route, so an agency key can provision its whole fleet.

Field Required Description
ig_user_id Yes The Instagram professional account ID — the user_id field from GET https://graph.instagram.com/v21.0/me?fields=user_id,username. This is the same ID Instagram webhooks carry as entry.id. ⚠️ It is not the id field from /me — that one is app-scoped and differs per Meta app. Pushing the app-scoped ID returns a 400 naming the mistake.
access_token Yes The long-lived Instagram user token your app obtained for that account. Validated live against Instagram before it is stored: the token must work and must belong to ig_user_id.
expires_at No ISO-8601 expiry of the token. Alternatively send expires_in (seconds). Defaults to 60 days.
username No The account’s @handle; we read it from Instagram anyway.
curl -X PUT "https://api.youraiconnector.com/v1/channels/instagram-login/token?apiKey=YOUR_AGENCY_KEY&sub_account_id=CLIENT_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "ig_user_id": "17841400000000000",
    "access_token": "IGAAR…",
    "expires_at": "2026-11-01T00:00:00Z"
  }'

Response

{
  "success": true,
  "ig_user_id": "17841400000000000",
  "username": "acme.studio",
  "expires_at": "2026-11-01T00:00:00.000Z",
  "webhook_url": "https://api.youraiconnector.com/v1/incoming-instagram-login-message/byo/YOUR_ACCOUNT_ID"
}

As part of the push we subscribe your app to that account’s webhooks (subscribed_apps with the pushed token), so messages start flowing without any extra call on your side.

Refreshing - push the refreshed token to the same endpoint with the same ig_user_id; it updates the stored token and expiry in place.

Conflicts - one Instagram account is never live on two connections. If the account is already connected elsewhere, or on this very account through the Facebook Page flow, the push returns a 409 telling you which connection to disconnect first. A Facebook-flow connection is never replaced automatically, because it may also be serving Messenger.

Step 3 - Disconnect when a client leaves

DELETE /channels/instagram-login/token (same auth and sub_account_id) unsubscribes the webhooks best-effort and removes the stored credential. It always succeeds, even when the token has already died — and once the credential is gone, that account’s webhook events are ignored.


Tips for building a reliable wrapper

  • Poll gently. Every few seconds is plenty. Stop once you reach a terminal state (connected / ONLINE, or a failure status), and put a sensible overall timeout on the loop (the browser/QR steps expire, see each expires_at).
  • URL-encode phone numbers in the path. The leading + should be sent as %2B. The endpoints recover bare digits too, but encoding is the safe default.
  • Never expect secrets back. Access tokens, channel secrets, and page tokens are accepted or stored but are never returned in any response.
  • Handle the auth gate. A 403 means API access isn’t on the plan, or that the channel you are connecting isn’t included in the account’s plan. See API Access.
  • Mind the rate limit. Authenticated requests are capped at 300 per minute; a 429 means back off and retry. See Authentication.

Next steps