Skip to content

OAuth Flow Guide ​

A step-by-step guide to connecting social media accounts to your Repliz workspace using the OAuth API.

Overview ​

Most platforms start the same way: you request an authorization URL, the user approves access in their browser, and the platform redirects back to your app with an authorization code. What you do with that code depends on the platform.

  • Direct flow (Instagram, Threads, TikTok, Shopee, X): send the code straight to Connect.
  • Flow with asset selection (Facebook, YouTube, LinkedIn): one login can manage several Pages, channels, or organizations, so you first exchange the code for a token, list the available assets, and then connect the one the user picks.

WhatsApp and Telegram have no authorization page or redirect. They use a third flow:

  • QR code flow (WhatsApp, Telegram): create a session, show its QR code, wait for the user to scan it with their phone, and then connect the account. On Telegram, an account with Two-Step Verification also sends its password before it connects.

Access Tier

The OAuth API is available on the Gold+ tier. All requests use Basic Auth (Authorization: Basic Base64(AccessKey:SecretKey)).

Which Flow Does My Platform Use? ​

PlatformFlowAsset to pickAsset list endpointID field on Connect
InstagramDirect---
ThreadsDirect---
TikTokDirect---
ShopeeDirect---
X (Twitter)Direct---
PinterestDirect---
FacebookWith asset selectionPageGet Page FacebookpageId
YouTubeWith asset selectionChannelGet Channel YouTubechannelId
LinkedInWith asset selectionPersonal account or organizationGet Organization LinkedInorganizationId
WhatsAppQR code---
TelegramQR code---

All endpoints follow the same path pattern, where {platform} is one of facebook, instagram, threads, youtube, linkedin, tiktok, shopee, twitter, pinterest, whatsapp, or telegram:

StepEndpointPlatforms
AuthorizeGET /public/account/{platform}/authorizeAll except WhatsApp and Telegram
ExchangePOST /public/account/{platform}/exchangeFacebook, YouTube, LinkedIn
Create sessionPOST /public/account/{platform}/sessionWhatsApp, Telegram
Get sessionGET /public/account/{platform}/sessionWhatsApp, Telegram
Send passwordPOST /public/account/telegram/session/passwordTelegram
List assetGET /public/account/facebook/pageFacebook
List assetGET /public/account/youtube/channelYouTube
List assetGET /public/account/linkedin/organizationLinkedIn
ConnectPOST /public/account/{platform}/connectAll
ReconnectPOST /public/account/{platform}/connect/{accountId}All

Direct Flow ​

Used by Instagram, Threads, TikTok, Shopee, X (Twitter), and Pinterest.

Authorize → User approves → Redirect with code → Connect / Reconnect

Step 1: Get the Authorization URL ​

Call Authorize with the redirect URL where the user should land after approving access. URL-encode the value if it contains its own query string.

bash
curl -X GET "https://api.repliz.com/public/account/tiktok/authorize?redirect=https://your-app.com/oauth/callback" \
  -H "Authorization: Basic $(echo -n 'YOUR_ACCESS_KEY:YOUR_SECRET_KEY' | base64)"
json
{
  "url": "https://www.tiktok.com/v2/auth/authorize?client_key=...&redirect_uri=https://your-app.com/oauth/callback&state=..."
}

Open the returned url in the user's browser, either as a full-page redirect or a popup. The user logs in and approves access on the platform's own page, so this step cannot run on your server alone.

Step 2: Handle the Redirect ​

After the user approves, the platform sends them back to your redirect URL. On most platforms the authorization code is in the query string:

https://your-app.com/oauth/callback?code=AUTH_CODE_FROM_REDIRECT&state=...

Read the code parameter on your callback page and pass it to your backend. You don't need to send state to Repliz.

Two platforms return the value in a different shape:

PlatformWhat the redirect containsSend as code
Facebook#access_token=... in the URL fragmentThe access_token value
Shopee?code=...&shop_id=... in the query stringBoth joined with an underscore: {code}_{shop_id}
Others?code=... in the query stringThe code value

The URL fragment (everything after #) never reaches your server, so for Facebook read it in the browser with window.location.hash and pass the value to your backend.

Use the code right away

Authorization codes are short-lived and can only be used once. If Connect fails because the code has expired or was already used, start again from Step 1.

Step 3: Connect the Account ​

Send the code to Connect. Repliz exchanges it with the platform and adds the account to your workspace.

bash
curl -X POST "https://api.repliz.com/public/account/tiktok/connect" \
  -H "Authorization: Basic $(echo -n 'YOUR_ACCESS_KEY:YOUR_SECRET_KEY' | base64)" \
  -H "Content-Type: application/json" \
  -d '{"code":"AUTH_CODE_FROM_REDIRECT"}'
json
{
  "accountId": "69e97477a795504e0786cec6"
}

Store the accountId. You will use it for schedules, automations, and any later reconnect.

To reconnect an existing account instead, send the same body to the Reconnect endpoint with the accountId in the path. It returns 204 No Content on success.

bash
curl -X POST "https://api.repliz.com/public/account/tiktok/connect/69e97477a795504e0786cec6" \
  -H "Authorization: Basic $(echo -n 'YOUR_ACCESS_KEY:YOUR_SECRET_KEY' | base64)" \
  -H "Content-Type: application/json" \
  -d '{"code":"AUTH_CODE_FROM_REDIRECT"}'

Pinterest ​

Pinterest uses the direct flow with pinterest in the path. The account you connect is the Pinterest user who approves access, not one of their boards. Boards are picked per post instead:

  1. Call Get Pinterest Board with the accountId to list the boards.
  2. Pass the chosen board as additionalInfo.channel in Create Schedule, and optionally the pin's destination URL as additionalInfo.link.
json
{
  "title": "Smart Comment Management",
  "description": "Hola from Repliz!",
  "type": "image",
  "medias": [{ "type": "image", "url": "https://storage.repliz.com/image.png", "thumbnail": "", "alt": "Repliz dashboard" }],
  "additionalInfo": {
    "channel": {
      "id": "1006921296753698211",
      "name": "Social Media Tips",
      "picture": "https://i.pinimg.com/400x300/fd/cd/d5/fdcdd5a6d8a80824add0d054125cd957.jpg",
      "type": "board"
    },
    "link": "https://repliz.com"
  },
  "accountId": "69e97477a795504e0786cec6",
  "scheduleAt": "2026-10-10T09:00:00.000Z"
}

Each schedule goes to one board, so create one schedule per board.

Flow with Asset Selection ​

Used by Facebook, YouTube, and LinkedIn. One login on these platforms can manage several assets: a Facebook user can manage many Pages, a Google account can own several YouTube channels, and a LinkedIn member can connect their personal account or any organization they administer. Each Repliz account maps to one asset, so the user has to pick which one to connect.

Authorize → User approves → Redirect with code → Exchange → Get Page / Channel / Organization → Connect / Reconnect

Step 1 & 2: Authorize and Handle the Redirect ​

These are the same as in the Direct Flow. Call the platform's Authorize endpoint, open the returned url in the browser, and read the value from your callback URL. For Facebook, that value is the access_token in the URL fragment; see Step 2.

bash
curl -X GET "https://api.repliz.com/public/account/facebook/authorize?redirect=https://your-app.com/oauth/callback" \
  -H "Authorization: Basic $(echo -n 'YOUR_ACCESS_KEY:YOUR_SECRET_KEY' | base64)"

Step 3: Exchange the Code for a Token ​

Send the code to Exchange. Unlike the direct flow, this does not connect anything yet. It returns a token that lets you list the user's assets.

bash
curl -X POST "https://api.repliz.com/public/account/facebook/exchange" \
  -H "Authorization: Basic $(echo -n 'YOUR_ACCESS_KEY:YOUR_SECRET_KEY' | base64)" \
  -H "Content-Type: application/json" \
  -d '{"code":"AUTH_CODE_FROM_REDIRECT"}'
json
{
  "token": "9402c20336a7c3ce2d8e4f76...2255a73f-8431-4458-abcf-ee7f867952db"
}

Step 4: List the Available Assets ​

Pass the token from Step 3 as a query parameter to the platform's list endpoint:

PlatformEndpoint
FacebookGET /public/account/facebook/page
YouTubeGET /public/account/youtube/channel
LinkedInGET /public/account/linkedin/organization
bash
curl -X GET "https://api.repliz.com/public/account/facebook/page?token=TOKEN_FROM_EXCHANGE" \
  -H "Authorization: Basic $(echo -n 'YOUR_ACCESS_KEY:YOUR_SECRET_KEY' | base64)"

All three endpoints return the same shape:

json
{
  "docs": [
    {
      "id": "113958387982006",
      "name": "Halaman ke 3",
      "username": "Kafe",
      "picture": "https://scontent.fbdo9-1.fna.fbcdn.net/v/t39.30808-1/...",
      "token": "PAGE_ACCESS_TOKEN"
    }
  ]
}
FieldDescription
idThe ID of the Page, channel, or LinkedIn account. Send it to Connect.
nameDisplay name, useful for showing a picker to the user.
usernameHandle or username of the asset.
pictureAvatar URL of the asset.
tokenAccess token tied to this asset. Send it to Connect.

LinkedIn personal accounts

For LinkedIn, the list includes the user's own personal account (urn:li:person:...) along with the organizations they administer (urn:li:organization:...). Both are connected the same way, by sending the id as organizationId.

Show the list to the user and let them choose which asset to connect. If docs is empty, the account the user logged in with doesn't manage any Page, channel, or organization.

Step 5: Connect the Selected Asset ​

Send the chosen item's id and token to Connect. The name of the ID field depends on the platform:

PlatformRequest body
Facebook{ "pageId": "...", "token": "..." }
YouTube{ "channelId": "...", "token": "..." }
LinkedIn{ "organizationId": "...", "token": "..." }
bash
curl -X POST "https://api.repliz.com/public/account/facebook/connect" \
  -H "Authorization: Basic $(echo -n 'YOUR_ACCESS_KEY:YOUR_SECRET_KEY' | base64)" \
  -H "Content-Type: application/json" \
  -d '{"pageId":"113958387982006","token":"PAGE_ACCESS_TOKEN"}'
json
{
  "accountId": "680affa5ce12f2f72916f67e"
}

To connect several assets, call Connect once for each selected item. Each call creates a separate account with its own accountId.

To reconnect an existing account, send the same body to the Reconnect endpoint with the accountId in the path. Pick the same Page, channel, or LinkedIn account the account was originally connected with. It returns 204 No Content on success.

bash
curl -X POST "https://api.repliz.com/public/account/facebook/connect/680affa5ce12f2f72916f67e" \
  -H "Authorization: Basic $(echo -n 'YOUR_ACCESS_KEY:YOUR_SECRET_KEY' | base64)" \
  -H "Content-Type: application/json" \
  -d '{"pageId":"113958387982006","token":"PAGE_ACCESS_TOKEN"}'

Full Example ​

Here is the complete flow for the direct and asset selection types in JavaScript. For WhatsApp, see the QR code flow example, and for Telegram, the Telegram example. getAuthorizeUrl runs before the redirect; handleCallback runs on your callback URL with the code from the query string.

javascript
import axios from 'axios';

const api = axios.create({
  baseURL: 'https://api.repliz.com/public/account',
  auth: {
    username: 'YOUR_ACCESS_KEY',
    password: 'YOUR_SECRET_KEY',
  },
});

// Platforms that need an asset picked after login, with their list endpoint and ID field
const ASSET_PLATFORMS = {
  facebook: { list: 'page', idField: 'pageId' },
  youtube: { list: 'channel', idField: 'channelId' },
  linkedin: { list: 'organization', idField: 'organizationId' },
};

// Read the value to send as `code` from your callback URL
function getCode(platform, callbackUrl) {
  const url = new URL(callbackUrl);
  // Facebook returns an access token in the URL fragment
  if (platform === 'facebook') {
    return new URLSearchParams(url.hash.slice(1)).get('access_token');
  }
  const code = url.searchParams.get('code');
  // Shopee needs the shop ID joined to the code
  if (platform === 'shopee') {
    return `${code}_${url.searchParams.get('shop_id')}`;
  }
  return code;
}

// Step 1: Get the authorization URL and send the user to it
async function getAuthorizeUrl(platform, redirect) {
  const { data } = await api.get(`/${platform}/authorize`, {
    params: { redirect },
  });
  return data.url;
}

// Step 2+: Called on your callback page with the code from the redirect.
// Pass accountId to reconnect an existing account instead of adding a new one.
async function handleCallback(platform, code, accountId, pickAsset) {
  const connectPath = accountId
    ? `/${platform}/connect/${accountId}`
    : `/${platform}/connect`;

  const asset = ASSET_PLATFORMS[platform];

  // Direct flow: Instagram, Threads, TikTok, Shopee, X
  if (!asset) {
    const { data } = await api.post(connectPath, { code });
    return data;
  }

  // Asset selection flow: Facebook, YouTube, LinkedIn
  const { data: exchange } = await api.post(`/${platform}/exchange`, { code });

  const { data: list } = await api.get(`/${platform}/${asset.list}`, {
    params: { token: exchange.token },
  });

  const selected = await pickAsset(list.docs); // let the user choose one

  const { data } = await api.post(connectPath, {
    [asset.idField]: selected.id,
    token: selected.token,
  });
  return data;
}

// Usage
const url = await getAuthorizeUrl('facebook', 'https://your-app.com/oauth/callback');
// ...user approves and lands on your callback URL, where you read the code
const code = getCode('facebook', window.location.href);

const result = await handleCallback('facebook', code, null, (docs) => docs[0]);
console.log('Connected account:', result.accountId);
javascript
const API_BASE = 'https://api.repliz.com/public/account';
const credentials = btoa('YOUR_ACCESS_KEY:YOUR_SECRET_KEY');

// Platforms that need an asset picked after login, with their list endpoint and ID field
const ASSET_PLATFORMS = {
  facebook: { list: 'page', idField: 'pageId' },
  youtube: { list: 'channel', idField: 'channelId' },
  linkedin: { list: 'organization', idField: 'organizationId' },
};

// Read the value to send as `code` from your callback URL
function getCode(platform, callbackUrl) {
  const url = new URL(callbackUrl);
  // Facebook returns an access token in the URL fragment
  if (platform === 'facebook') {
    return new URLSearchParams(url.hash.slice(1)).get('access_token');
  }
  const code = url.searchParams.get('code');
  // Shopee needs the shop ID joined to the code
  if (platform === 'shopee') {
    return `${code}_${url.searchParams.get('shop_id')}`;
  }
  return code;
}

async function request(path, { method = 'GET', body } = {}) {
  const response = await fetch(`${API_BASE}${path}`, {
    method,
    headers: {
      Authorization: `Basic ${credentials}`,
      'Content-Type': 'application/json',
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
  return response.status === 204 ? null : response.json();
}

// Step 1: Get the authorization URL and send the user to it
async function getAuthorizeUrl(platform, redirect) {
  const data = await request(
    `/${platform}/authorize?redirect=${encodeURIComponent(redirect)}`,
  );
  return data.url;
}

// Step 2+: Called on your callback page with the code from the redirect.
// Pass accountId to reconnect an existing account instead of adding a new one.
async function handleCallback(platform, code, accountId, pickAsset) {
  const connectPath = accountId
    ? `/${platform}/connect/${accountId}`
    : `/${platform}/connect`;

  const asset = ASSET_PLATFORMS[platform];

  // Direct flow: Instagram, Threads, TikTok, Shopee, X
  if (!asset) {
    return request(connectPath, { method: 'POST', body: { code } });
  }

  // Asset selection flow: Facebook, YouTube, LinkedIn
  const exchange = await request(`/${platform}/exchange`, {
    method: 'POST',
    body: { code },
  });

  const list = await request(
    `/${platform}/${asset.list}?token=${encodeURIComponent(exchange.token)}`,
  );

  const selected = await pickAsset(list.docs); // let the user choose one

  return request(connectPath, {
    method: 'POST',
    body: { [asset.idField]: selected.id, token: selected.token },
  });
}

// Usage
const url = await getAuthorizeUrl('facebook', 'https://your-app.com/oauth/callback');
// ...user approves and lands on your callback URL, where you read the code
const code = getCode('facebook', window.location.href);

const result = await handleCallback('facebook', code, null, (docs) => docs[0]);
console.log('Connected account:', result.accountId);

QR Code Flow ​

Used by WhatsApp and Telegram. There is no consent page to redirect to. Instead, Repliz links itself to the user's account as a device, the same way WhatsApp Web or Telegram Desktop does: you show a QR code and the user scans it from their phone. The scan connects that WhatsApp number or Telegram account as one Repliz account.

The steps below use WhatsApp. Telegram follows them with telegram in the path, plus a password step for accounts with Two-Step Verification.

Create Session → Poll Get Session and show the QR code → User scans → Connect / Reconnect

Step 1: Create a Session ​

Call Create Session. It returns a session token that the other WhatsApp calls take.

bash
curl -X POST "https://api.repliz.com/public/account/whatsapp/session" \
  -H "Authorization: Basic $(echo -n 'YOUR_ACCESS_KEY:YOUR_SECRET_KEY' | base64)"
json
{
  "token": "a2ee814b0a49d6a55d07a73db97578387e1cb226...8bf0181672039791e2b92e1e77347c86fc5cab9f8051a3a8ebbdde7db3dee9"
}

Step 2: Poll the Session and Show the QR Code ​

Call Get Session with the token every 2 seconds.

bash
curl -X GET "https://api.repliz.com/public/account/whatsapp/session?token=SESSION_TOKEN" \
  -H "Authorization: Basic $(echo -n 'YOUR_ACCESS_KEY:YOUR_SECRET_KEY' | base64)"
json
{
  "id": "6a38d93d824af0f1182e7486",
  "generatedId": "",
  "name": "",
  "sessionId": "533e6fca-8c88-48c0-86c2-438d43fbc986",
  "isConnected": false,
  "qrcode": "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAABlBMVEX///8AAABVwtN+AAAE...",
  "picture": ""
}

While isConnected is false, show qrcode as an image by prefixing it with data:image/png;base64,. Ask the user to open WhatsApp on their phone, tap Linked devices (in the ⋮ menu on Android, or in Settings on iPhone), then Link a device, and scan the code.

  • Update the image on every poll. WhatsApp replaces the code every 20 to 60 seconds, and an old code can't be scanned.
  • qrcode can be empty for a moment right after the session is created. Keep polling until it appears.
  • Stop when isConnected is true. qrcode is empty again and name, picture, and generatedId now describe the number that scanned. Continue to Step 3.
  • 404 session not found means the code expired before anyone scanned it. Go back to Step 1 for a new session. Also stop polling after about 4 minutes, so a page the user abandoned doesn't poll forever.

Step 3: Connect the Number ​

Send the token to Connect:

bash
curl -X POST "https://api.repliz.com/public/account/whatsapp/connect" \
  -H "Authorization: Basic $(echo -n 'YOUR_ACCESS_KEY:YOUR_SECRET_KEY' | base64)" \
  -H "Content-Type: application/json" \
  -d '{"token":"SESSION_TOKEN"}'
json
{
  "accountId": "69e97477a795504e0786cec6"
}

Connecting a number that is already in your workspace updates that account instead of adding a duplicate. Connect returns 403 invalid status session if the QR code hasn't been scanned yet.

To reconnect an account, run Steps 1 and 2 again so the user scans a new QR code, then send the new token to the Reconnect endpoint with the accountId in the path. It returns 204 No Content on success. The user has to scan with the number the account belongs to; any other number returns 400 incorrect generatedId to reconnect.

bash
curl -X POST "https://api.repliz.com/public/account/whatsapp/connect/69e97477a795504e0786cec6" \
  -H "Authorization: Basic $(echo -n 'YOUR_ACCESS_KEY:YOUR_SECRET_KEY' | base64)" \
  -H "Content-Type: application/json" \
  -d '{"token":"NEW_SESSION_TOKEN"}'

Posting to a Channel or Group ​

The WhatsApp Channels the number owns and the groups it has joined are not separate accounts. A schedule for the WhatsApp account goes to the number's Status unless it names a Channel or group:

  1. Call Get WhatsApp Channel with the accountId to list the Channels and groups.
  2. Pass the chosen item as additionalInfo.channel in Create Schedule.
json
{
  "title": "",
  "description": "Hola from Repliz!",
  "type": "text",
  "medias": [],
  "additionalInfo": {
    "channel": {
      "id": "120363413774168194@newsletter",
      "name": "Boom",
      "picture": "https://pps.whatsapp.net/m1/v/t24/An-6sQyuS8Q9UAjgMTbC8PQ5J3fWqldErtluDIBugLBhJk7llbTd2XIQT...",
      "type": "channel"
    }
  },
  "accountId": "69e97477a795504e0786cec6",
  "scheduleAt": "2026-10-10T09:00:00.000Z"
}

Each schedule goes to one place, so create one schedule per Channel or group.

Telegram ​

Telegram uses the same steps with telegram in the path instead of whatsapp, plus one extra step for accounts protected by Two-Step Verification.

  1. Create a session with POST /public/account/telegram/session.

  2. Poll Get Session (GET /public/account/telegram/session?token=...) and show qrcode. Ask the user to open Telegram on their phone, go to Settings → Devices, tap Link Desktop Device, and scan the code. Telegram replaces the code about every 30 seconds, and the session is deleted if nobody scans it within about three minutes.

  3. Send the password if asked. After the scan, an account with Two-Step Verification returns isPasswordRequired: true and an empty qrcode. Ask the user for their password, showing passwordHint if it isn't empty, and send it to Password Session Telegram:

    bash
    curl -X POST "https://api.repliz.com/public/account/telegram/session/password" \
      -H "Authorization: Basic $(echo -n 'YOUR_ACCESS_KEY:YOUR_SECRET_KEY' | base64)" \
      -H "Content-Type: application/json" \
      -d '{"token":"SESSION_TOKEN","password":"USER_PASSWORD"}'

    It returns 204 No Content when Telegram accepts the password, and 400 invalid password when it doesn't, so the user can try again with the same session. Keep polling Get Session either way.

  4. Connect with POST /public/account/telegram/connect once isConnected is true, or Reconnect with POST /public/account/telegram/connect/{accountId}. A different Telegram account returns 400 incorrect generatedId to reconnect.

A schedule for the Telegram account goes to its Story unless it names a channel or group. List them with Get Telegram Channel and pass the chosen item as additionalInfo.channel, including its type: Telegram channel and supergroup IDs look the same, so type decides whether the post goes to a channel or a group.

The connected account also brings its private chats into the Chat API, where the chat module of account automation can answer them.

Telegram Example ​

waitForLogin covers the scan and the password. askPassword receives the hint and must resolve to the password the user typed.

javascript
import axios from 'axios';

const api = axios.create({
  baseURL: 'https://api.repliz.com/public/account/telegram',
  auth: {
    username: 'YOUR_ACCESS_KEY',
    password: 'YOUR_SECRET_KEY',
  },
});

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// Create a session and wait until the login is complete.
// Throws on 404 if the code expired; call it again to show a new one.
async function waitForLogin(showQRCode, askPassword, { interval = 2000, timeout = 4 * 60 * 1000 } = {}) {
  const { data } = await api.post('/session');
  let deadline = Date.now() + timeout;
  let lastQRCode = '';

  while (Date.now() < deadline) {
    const { data: session } = await api.get('/session', {
      params: { token: data.token },
    });

    if (session.isConnected) return data.token;

    if (session.isPasswordRequired) {
      const password = await askPassword(session.passwordHint);
      try {
        await api.post('/session/password', { token: data.token, password });
      } catch (error) {
        // A wrong password leaves isPasswordRequired on, so the next poll asks again
        if (error.response?.status !== 400) throw error;
      }
      // Typing the password doesn't count against the scan timeout
      deadline = Date.now() + timeout;
    } else if (session.qrcode && session.qrcode !== lastQRCode) {
      // The code changes about every 30 seconds, so redraw it whenever it does
      lastQRCode = session.qrcode;
      showQRCode(`data:image/png;base64,${session.qrcode}`);
    }

    await sleep(interval);
  }

  throw new Error('The QR code was not scanned in time');
}

async function connectTelegram(showQRCode, askPassword) {
  const token = await waitForLogin(showQRCode, askPassword);
  const { data } = await api.post('/connect', { token });
  return data.accountId;
}

async function reconnectTelegram(accountId, showQRCode, askPassword) {
  const token = await waitForLogin(showQRCode, askPassword);
  await api.post(`/connect/${accountId}`, { token });
}

// Usage
const accountId = await connectTelegram(
  (src) => {
    document.querySelector('#telegram-qr').src = src;
  },
  async (hint) => window.prompt(hint ? `Telegram password (hint: ${hint})` : 'Telegram password'),
);
console.log('Connected account:', accountId);
javascript
const API_BASE = 'https://api.repliz.com/public/account/telegram';
const credentials = btoa('YOUR_ACCESS_KEY:YOUR_SECRET_KEY');

async function request(path, { method = 'GET', body } = {}) {
  const response = await fetch(`${API_BASE}${path}`, {
    method,
    headers: {
      Authorization: `Basic ${credentials}`,
      'Content-Type': 'application/json',
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!response.ok) {
    const error = new Error(`${response.status} ${await response.text()}`);
    error.status = response.status;
    throw error;
  }
  return response.status === 204 ? null : response.json();
}

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// Create a session and wait until the login is complete.
// Throws on 404 if the code expired; call it again to show a new one.
async function waitForLogin(showQRCode, askPassword, { interval = 2000, timeout = 4 * 60 * 1000 } = {}) {
  const { token } = await request('/session', { method: 'POST' });
  let deadline = Date.now() + timeout;
  let lastQRCode = '';

  while (Date.now() < deadline) {
    const session = await request(`/session?token=${encodeURIComponent(token)}`);

    if (session.isConnected) return token;

    if (session.isPasswordRequired) {
      const password = await askPassword(session.passwordHint);
      try {
        await request('/session/password', { method: 'POST', body: { token, password } });
      } catch (error) {
        // A wrong password leaves isPasswordRequired on, so the next poll asks again
        if (error.status !== 400) throw error;
      }
      // Typing the password doesn't count against the scan timeout
      deadline = Date.now() + timeout;
    } else if (session.qrcode && session.qrcode !== lastQRCode) {
      // The code changes about every 30 seconds, so redraw it whenever it does
      lastQRCode = session.qrcode;
      showQRCode(`data:image/png;base64,${session.qrcode}`);
    }

    await sleep(interval);
  }

  throw new Error('The QR code was not scanned in time');
}

async function connectTelegram(showQRCode, askPassword) {
  const token = await waitForLogin(showQRCode, askPassword);
  const data = await request('/connect', { method: 'POST', body: { token } });
  return data.accountId;
}

async function reconnectTelegram(accountId, showQRCode, askPassword) {
  const token = await waitForLogin(showQRCode, askPassword);
  await request(`/connect/${accountId}`, { method: 'POST', body: { token } });
}

// Usage
const accountId = await connectTelegram(
  (src) => {
    document.querySelector('#telegram-qr').src = src;
  },
  async (hint) => window.prompt(hint ? `Telegram password (hint: ${hint})` : 'Telegram password'),
);
console.log('Connected account:', accountId);

QR Code Flow Example ​

waitForScan covers Steps 1 and 2 and calls showQRCode whenever the code changes. connectWhatsApp and reconnectWhatsApp build on it.

javascript
import axios from 'axios';

const api = axios.create({
  baseURL: 'https://api.repliz.com/public/account/whatsapp',
  auth: {
    username: 'YOUR_ACCESS_KEY',
    password: 'YOUR_SECRET_KEY',
  },
});

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// Steps 1 and 2: create a session and wait until the user scans its QR code.
// Throws on 404 if the code expired; call it again to show a new one.
async function waitForScan(showQRCode, { interval = 2000, timeout = 4 * 60 * 1000 } = {}) {
  const { data } = await api.post('/session');
  const deadline = Date.now() + timeout;
  let lastQRCode = '';

  while (Date.now() < deadline) {
    const { data: session } = await api.get('/session', {
      params: { token: data.token },
    });

    if (session.isConnected) return data.token;

    // The code changes every 20 to 60 seconds, so redraw it whenever it does
    if (session.qrcode && session.qrcode !== lastQRCode) {
      lastQRCode = session.qrcode;
      showQRCode(`data:image/png;base64,${session.qrcode}`);
    }

    await sleep(interval);
  }

  throw new Error('The QR code was not scanned in time');
}

// Step 3: connect the number that scanned the code
async function connectWhatsApp(showQRCode) {
  const token = await waitForScan(showQRCode);
  const { data } = await api.post('/connect', { token });
  return data.accountId;
}

// Reconnect: same scan, then send the token with the existing accountId
async function reconnectWhatsApp(accountId, showQRCode) {
  const token = await waitForScan(showQRCode);
  await api.post(`/connect/${accountId}`, { token });
}

// Usage
const accountId = await connectWhatsApp((src) => {
  document.querySelector('#whatsapp-qr').src = src;
});
console.log('Connected account:', accountId);
javascript
const API_BASE = 'https://api.repliz.com/public/account/whatsapp';
const credentials = btoa('YOUR_ACCESS_KEY:YOUR_SECRET_KEY');

async function request(path, { method = 'GET', body } = {}) {
  const response = await fetch(`${API_BASE}${path}`, {
    method,
    headers: {
      Authorization: `Basic ${credentials}`,
      'Content-Type': 'application/json',
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
  return response.status === 204 ? null : response.json();
}

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// Steps 1 and 2: create a session and wait until the user scans its QR code.
// Throws on 404 if the code expired; call it again to show a new one.
async function waitForScan(showQRCode, { interval = 2000, timeout = 4 * 60 * 1000 } = {}) {
  const { token } = await request('/session', { method: 'POST' });
  const deadline = Date.now() + timeout;
  let lastQRCode = '';

  while (Date.now() < deadline) {
    const session = await request(`/session?token=${encodeURIComponent(token)}`);

    if (session.isConnected) return token;

    // The code changes every 20 to 60 seconds, so redraw it whenever it does
    if (session.qrcode && session.qrcode !== lastQRCode) {
      lastQRCode = session.qrcode;
      showQRCode(`data:image/png;base64,${session.qrcode}`);
    }

    await sleep(interval);
  }

  throw new Error('The QR code was not scanned in time');
}

// Step 3: connect the number that scanned the code
async function connectWhatsApp(showQRCode) {
  const token = await waitForScan(showQRCode);
  const data = await request('/connect', { method: 'POST', body: { token } });
  return data.accountId;
}

// Reconnect: same scan, then send the token with the existing accountId
async function reconnectWhatsApp(accountId, showQRCode) {
  const token = await waitForScan(showQRCode);
  await request(`/connect/${accountId}`, { method: 'POST', body: { token } });
}

// Usage
const accountId = await connectWhatsApp((src) => {
  document.querySelector('#whatsapp-qr').src = src;
});
console.log('Connected account:', accountId);

Connect vs Reconnect ​

UseWhenResult
ConnectAdding an account that is not in your workspace yet.200 with a new accountId
ReconnectAn existing account lost access, for example the token expired or the user revoked the app.204 No Content, same accountId

Reconnect keeps the same accountId, so schedules, automations, and anything else that references the account keep working. Use Get Account or Get One Account to check the isConnected field and find accounts that need a reconnect.

Both run the same flow for the platform. The only difference is the final request: Reconnect adds /{accountId} to the Connect path.

Common Errors ​

StatusMessageCause and fix
400incorrect generatedId to reconnectThe account the user logged in with, or the asset they picked, is not the one linked to accountId. Log in with the same platform account and pick the same Page, channel, or LinkedIn account. For WhatsApp, scan the QR code with the number the account belongs to, and for Telegram, with the same Telegram account.
404account not foundThe accountId on Reconnect doesn't exist in your workspace.
400invalid passwordTelegram only. The Two-Step Verification password is wrong. Ask the user again and resend it with the same session token.
403invalid status sessionWhatsApp and Telegram only. The login isn't complete yet: the QR code hasn't been scanned, or on Telegram the password hasn't been sent. Keep polling Get Session until isConnected is true before calling Connect or Reconnect.
404session not foundWhatsApp and Telegram only. The session expired because nobody scanned the QR code in time. Create a new session.
402please upgrade plan to connect more accountConnect only. Your workspace has reached its plan's account limit.
401unauthorizedYour Access Key or Secret Key is wrong. See Install.