Connecting Accounts

Before you can post, your users need to connect their social media accounts. This guide walks through every connection method — OAuth, App Password, and Bot Token — including the entity selection step required by some platforms.

Prerequisites: You need an API key with accounts permission. Connected accounts are grouped under a brand; pass brandId to target a specific brand, or omit it to use your default brand. See Authentication and the Accounts API.

Connected account limits: Each plan includes a set number of connected accounts (Free: 2, Personal: 10, Team: 25, Agency: 100). A connected account is one profile on one platform. Connecting a new account when you're at the limit returns an account_limit_reached error. Upgrade your plan to connect more. See Rate Limits for details.

Connection methods

MethodPlatformsFlow
OAuth 2.0Twitter, Instagram, Facebook, Threads, LinkedIn, TikTok, YouTube, Pinterest, Google Business ProfileGET redirect URL → user authorizes → callback
App PasswordBlueskyPOST credentials directly
Bot TokenTelegramPOST bot token → register channel(s)
Dashboard onlyMastodonConnect on the Schedulala dashboard (per-instance OAuth), then post via the API

Mastodon is the one exception: its OAuth is per-instance, so the API connect endpoint does not support it yet. Connect the account once on the Schedulala dashboard and it becomes available to the API immediately (posting, accounts, analytics).

OAuth flow (9 platforms)

Twitter, Instagram, Facebook, Threads, LinkedIn, TikTok, YouTube, Pinterest, and Google Business Profile all use the same redirect-based OAuth flow. For Google Business Profile, the first business location on the Google account is connected automatically.

Step 1: Get the OAuth URL

Request an authorization URL for the platform. Both query parameters are optional: brandId defaults to your default brand, and redirectUrl (where the user is sent after authorizing) defaults to Schedulala's hosted completion page.

curl "https://schedulala.com/api/v1/connect/instagram?brandId=brand_abc123&redirectUrl=https://yourapp.com/callback" \
-H "Authorization: Bearer sk_live_abc123xyz789..."

Step 2: Redirect the user

Open the authUrl in the user's browser (or a popup/webview). The user will authenticate with the platform and grant your app access. After authorizing, the platform redirects back to your redirectUrl with the connection result.

Important: The redirectUrl is where your user lands after the platform authorization completes. If you omit it, the user lands on Schedulala's hosted completion page at /developers/oauth-complete.

Step 3: Handle the callback

When the user is redirected back, check the query parameters. A successful connection includes success=true and the account_id. Platforms that need a sub-entity choice instead return success=pending with selectionRequired=true and a selectionToken; see the next section.

Callback handler example
// In your callback route (e.g. /callback)
const url = new URL(request.url);
const success = url.searchParams.get('success');
const accountId = url.searchParams.get('account_id');
const selectionRequired = url.searchParams.get('selectionRequired');
const selectionToken = url.searchParams.get('selectionToken');
if (success === 'true') {
// Account is fully connected and ready to post
console.log('Connected account:', accountId);
} else if (success === 'pending' && selectionRequired === 'true') {
// Need to select an entity (page, org, channel, board)
// Use selectionToken with the /entities and /select endpoints below
} else {
const error = url.searchParams.get('error');
const message = url.searchParams.get('message');
console.error('Connection failed:', error, message);
}

Entity selection

Four platforms require selecting a sub-entity after the initial OAuth connection: Facebook (Pages), LinkedIn (Organizations), YouTube (Channels), and Pinterest (Boards). Until an entity is selected, the account cannot be used for posting. Telegram uses a separate channel registration flow; see the Telegram section below.

List available entities

Pass the selectionToken from the OAuth callback. Selection sessions expire after 10 minutes.

curl "https://schedulala.com/api/v1/connect/facebook/entities?selectionToken=sel_abc123..." \
-H "Authorization: Bearer sk_live_abc123xyz789..."

Select an entity

Once the user picks an entity, call the select endpoint with the selectionToken and the entity's id. After this, the account is fully connected and ready for posting.

curl -X POST "https://schedulala.com/api/v1/connect/facebook/select" \
-H "Authorization: Bearer sk_live_abc123xyz789..." \
-H "Content-Type: application/json" \
-d '{
"selectionToken": "sel_abc123...",
"entityId": "page_123"
}'

Bluesky (App Password)

Bluesky doesn't use OAuth. Instead, users provide their handle (sent as identifier) and an app password generated from Bluesky settings. The connection is established in a single POST request.

curl -X POST "https://schedulala.com/api/v1/connect/bluesky" \
-H "Authorization: Bearer sk_live_abc123xyz789..." \
-H "Content-Type: application/json" \
-d '{
"brandId": "brand_abc123",
"identifier": "yourhandle.bsky.social",
"appPassword": "xxxx-xxxx-xxxx-xxxx"
}'

Security: App passwords should be treated like API keys. Never log them or expose them in client-side code. Encourage your users to create a dedicated app password for your integration.

Telegram (Bot Token)

Telegram uses a Bot Token flow. Your user creates a bot via @BotFather, adds the bot as an administrator to each channel they want to post to, then provides the token. Because Telegram has no API to list a bot's channels, you register each channel explicitly by its chat id or @username.

1

Create a bot

Message @BotFather on Telegram with /newbot and follow the prompts to get a bot token.

2

Add bot to the channel

Add the bot as an administrator with permission to post messages to each Telegram channel you want to post to.

3

Connect and register channels

POST the bot token (optionally with channels to register at the same time), then register more channels later with the channels endpoint.

A bot with no registered channel cannot post. Registration is explicit because Telegram cannot list a bot's channels. When you connect with no channels, the response includes a notice telling you to add one. At post time, Schedulala posts to the channel named by platformSettings.telegram.channelId (a chat id or @username), or the first active registered channel if you don't name one.

Step 1: Connect with the bot token

POST the botToken. You can optionally include a channels array (up to 10 chat ids or @usernames) to register them in the same request. Each channel is validated against the Telegram API, confirming the bot is an administrator that can post.

curl -X POST "https://schedulala.com/api/v1/connect/telegram" \
-H "Authorization: Bearer sk_live_abc123xyz789..." \
-H "Content-Type: application/json" \
-d '{
"brandId": "brand_abc123",
"botToken": "7123456789:AAH...",
"channels": ["@yourchannel"]
}'

Response when no channels are registered:

{
"success": true,
"account": {
"id": "67abc456def789012345cdef",
"platform": "telegram",
"username": "yourbot",
"displayName": "Your Bot",
"isConnected": true,
"channels": []
},
"notice": "No channels registered yet. The bot cannot post until you add one via POST /api/v1/connect/telegram/channels."
}

Step 2: Register a channel

Register a channel by its numeric chat id or @username. Schedulala validates that the bot is an administrator with permission to post in the channel before saving it. Registering the same channel twice returns a conflict (409). Pass accountId when you have more than one connected Telegram bot.

curl -X POST "https://schedulala.com/api/v1/connect/telegram/channels" \
-H "Authorization: Bearer sk_live_abc123xyz789..." \
-H "Content-Type: application/json" \
-d '{
"channel": "@yourchannel"
}'

List registered channels

Retrieve the channels currently registered on a connected bot. Pass accountId to target a specific bot when you have more than one.

curl "https://schedulala.com/api/v1/connect/telegram/channels" \
-H "Authorization: Bearer sk_live_abc123xyz789..."

Health checks

Connections can break over time — tokens expire, users revoke access, or passwords change. Use health checks to proactively detect issues before posts fail.

Single account

curl "https://schedulala.com/api/v1/accounts/acc_ig_123/health" \
-H "Authorization: Bearer sk_live_abc123xyz789..."

Bulk health check

Check all accounts at once. Useful for a connection status dashboard.

curl "https://schedulala.com/api/v1/accounts/health" \
-H "Authorization: Bearer sk_live_abc123xyz789..."

Tip: Run bulk health checks daily (e.g., via cron) and alert your users when a connection is unhealthy. This prevents silent post failures and reduces support tickets.

Disconnecting accounts

Warning: Disconnecting an account clears its stored tokens. Scheduled posts targeting it will fail to publish unless the account is reconnected.

curl -X DELETE "https://schedulala.com/api/v1/accounts/acc_ig_123" \
-H "Authorization: Bearer sk_live_abc123xyz789..."

Troubleshooting

Common connection errors and how to resolve them.

ErrorCauseSolution
oauth_deniedUser denied the OAuth prompt on the platformRetry the OAuth flow and have the user approve access.
conflictAccount is already connected to this brandDisconnect the existing account first, then reconnect.
authentication_failedWrong app password (Bluesky) or invalid bot token (Telegram)Double-check the credentials. For Bluesky, generate a new app password. For Telegram, verify the bot token with @BotFather.
expired (health status)Health check reports the OAuth token expired and refresh failedReconnect the account by running the OAuth flow again. Schedulala auto-refreshes tokens, but some platforms revoke tokens after long inactivity.
not_found (selection session)More than 10 minutes passed between OAuth and entity selection, so the selection session expiredRestart the connection flow, then call the /entities and /select endpoints within 10 minutes.
token_exchange_failedThe platform rejected the token exchange during the OAuth callbackCheck the platform's status page and retry the OAuth flow.

Related