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
- Your server calls
POST /api/v1/profiles/:profileId/connectand gets back anauthUrlplus a session id. - You send your customer's browser to
authUrl. They authorize with the provider directly. - The provider returns to Adeli, which exchanges the code server-side and stores the token.
- Adeli sends the browser back to your
redirectUrlwith the session id and status in the query string. - 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
/api/v1/profiles/{profileId}/connectStart a hosted OAuth connection and get the URL to send your customer to.GETGet connection session/api/v1/profiles/{profileId}/connect/{connectionSessionId}Poll a connection session until it completes, fails, or expires.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-accountsfor Instagram and Facebook,connecting-tiktokfor TikTok,connecting-youtubefor 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 theinstagram_business_*permissions above.facebook_login— your customer signs in with Facebook and grants the Facebook Page their Instagram account is linked to. Adeli requestsinstagram_basic,instagram_content_publish,instagram_manage_comments,instagram_manage_insights,instagram_manage_messages,pages_show_list,pages_read_engagement,pages_manage_metadata, andbusiness_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
handledid not resolve to a Bluesky account. Starting the connect returns400. 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.