Connect

The connect flow lets your customer authorize their Instagram, Facebook, TikTok, YouTube, X, or Bluesky account from your application. They never see an Adeli dashboard, and you never handle a provider token.

How it works

  1. Your server calls POST /api/v1/profiles/:profileId/connect and gets back an authUrl plus a session id.
  2. You send your customer's browser to authUrl. They authorize with the provider directly.
  3. The provider returns to Adeli, which exchanges the code server-side and stores the token.
  4. Adeli sends the browser back to your redirectUrl with the session id and status in the query string.
  5. Your server polls the session until it reaches a terminal state.

Open authUrl in the customer's browser

Do not fetch it from your backend. It is an authorization page that requires a human to grant consent; requesting it server-side will not produce a connection.

The session is valid for ten minutes and its OAuth state can be consumed once. A callback that arrives late, twice, or with a tampered state is rejected without re-exchanging anything with the provider.

For the full internal sequence, see the repository's docs/nested-oauth-connect-flow.md.

Endpoints

The browser return

When you supply a redirectUrl, Adeli sends the browser back with these query parameters:

connectionSessionIduuid
The session to poll.
statusstring
The status at redirect time.
accountIduuid
Present only on success.
workflowIdstring
An internal hint — connecting-accounts for Instagram and Facebook, connecting-tiktok for TikTok, connecting-youtube for YouTube.

Poll, do not trust the query string

These parameters are convenient for rendering an immediate result in your UI, but they arrive through the customer's browser. Confirm the outcome with a server-side poll before acting on it.

Provider requirements

Instagram needs a professional (Business or Creator) account. Adeli requests instagram_business_basic, instagram_business_manage_comments, instagram_business_manage_insights, instagram_business_manage_messages, and instagram_business_content_publish.

Instagram login methods

An Instagram professional account can be connected two ways, and a profile's Instagram connection uses exactly one of them:

  • instagram_login (the default) — your customer signs in on Instagram. Adeli requests the instagram_business_* permissions above.
  • facebook_login — your customer signs in with Facebook and grants the Facebook Page their Instagram account is linked to. Adeli requests instagram_basic, instagram_content_publish, instagram_manage_comments, instagram_manage_insights, instagram_manage_messages, pages_show_list, pages_read_engagement, pages_manage_metadata, and business_management. They must be able to manage messages on that Page.

Both produce the same account: posting, comments, messages, and insights work identically, and Accounts reports which method it uses.

Starting a connect with the method the profile's Instagram does not use returns 409 auth_method_conflict before your customer reaches Meta. To switch, disconnect the account first. A Facebook Login session that cannot finish fails with one of these error.code values:

no_instagram_account
None of the Pages your customer granted has an Instagram professional account linked.
multiple_instagram_accounts
More than one was granted. Start again and choose only one.
missing_permissions
Facebook did not grant a permission the connection needs.
page_messaging_task_missing
Your customer's role on the Page cannot manage messages.
auth_method_conflict
The profile was connected with Instagram Login while this session was open.

Facebook connects one Page. Adeli uses Facebook Login for Business and requests public_profile, pages_show_list, pages_read_engagement, pages_manage_metadata, pages_manage_posts, pages_read_user_content, pages_manage_engagement, and pages_messaging.

Facebook is the one provider whose connection is not finished when the authorization callback returns. Authorizing yields a user token, which lists the Pages your customer manages; a Page still has to be chosen before anything can be published or read. Until it is, the session stays processing and the redirect carries status=pending_page_selection. The account exists at that point and appears in Accounts with connectionStatus: "pending_page_selection", but every write against it returns 409 page_not_selected.

Only Pages your customer holds a direct role on are offered. A Page granted to them solely through a business portfolio is not connectable, because Adeli does not request business_management.

TikTok connects through TikTok API for Business and requests user.info.basic, user.info.username, user.info.profile, user.info.stats, user.account.type, user.insights, video.list, video.insights, video.publish, video.upload, comment.list, comment.list.manage, and biz.spark.auth. Both personal accounts and TikTok Business Accounts can connect. The account identity and video list scopes — the first five and video.list — are required; without them the connection fails outright. The rest are stored as granted, and a call that needs a missing one returns 422 connection_expired until the customer reconnects and approves it. TikTok always shows its consent screen, even to a customer who authorized before.

TikTok connections made before Adeli moved to TikTok API for Business show reconnect_required and must be connected again. Reconnecting keeps the same accountId.

YouTube connects one channel through its own Google sign-in, separate from Adeli's dashboard sign-in. Adeli requests https://www.googleapis.com/auth/youtube.force-ssl, which covers uploads, comments, and moderation, and https://www.googleapis.com/auth/yt-analytics.readonly for YouTube analytics. Google's account chooser decides which channel is connected: your customer's own, or one of their Brand Account channels. Reconnecting the same channel keeps the same accountId; connecting a different channel on the profile replaces it. Google always shows its consent screen. A YouTube session that cannot finish fails with one of these error.code values:

missing_scope
Your customer unticked the YouTube permission on Google's consent screen. Nothing is stored.
no_channel
The Google account chosen has no YouTube channel. Start again and choose the account or Brand Account that owns the channel.

The analytics scope is optional: a connection without it keeps every other feature, and the analytics endpoints return 422 connection_expired until the customer reconnects and approves it. Google revokes a grant when your customer removes Adeli from their Google account, and after six months unused; the account then shows reconnect_required.

X connects one account through Adeli's X app with OAuth 2.0. Adeli requests tweet.read, tweet.write, users.read, media.write, and offline.access, all required: a session where your customer withholds one fails with missing_scope and stores nothing. Reconnecting the same X account keeps the same accountId; connecting a different one on the profile replaces it.

X shows every customer a warning that the app is requesting sensitive permissions and is not affiliated with X. It appears for any app that can post, and cannot be removed, so tell your customers to expect it.

Every X call is billed to you at X's price, including the one read of the account's profile that connecting makes. Until the workspace has a card on file, starting a connect returns 402 x_billing_required before any authUrl exists. That applies even within the free accounts. See X API pricing.

Instagram and Facebook both accept partial consent: the connection is stored with whatever was granted, and the calls needing a missing permission return 403 missing_permission.

Bluesky

Bluesky connects one account with AT Protocol OAuth. Your customer signs in on their account's own server: bsky.social for Bluesky-hosted accounts, which is where a connect without handle starts. A customer who runs their own server, or uses another provider's, must be connected with handle, or they won't find their account at bsky.social.

The consent screen names Adeli and asks for broad access to the account, because the permissions Bluesky offers today are not finer-grained than that. Adeli uses it to publish, list, and delete posts. It does not read direct messages. Reconnecting the same account keeps the same accountId; connecting a different one on the profile replaces it. Bluesky's API is free: connecting and posting cost nothing beyond the connected account itself.

error.code values beyond the common ones:

invalid_handle
The handle did not resolve to a Bluesky account. Starting the connect returns 400.
missing_scope
The grant lacked a permission Adeli needs. It was revoked, and nothing is stored.

A customer can revoke Adeli in the Bluesky app's settings at any time; the account then shows reconnect_required.

Disconnecting

DELETE /api/v1/profiles/:profileId/accounts/:accountId removes Adeli's stored credentials and cached history. See Accounts. It never deletes anything on the provider side.