Connect
The connect flow lets your customer authorize their Instagram, Facebook, TikTok, YouTube, or X 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.
POST/api/v1/profiles/{profileId}/connectStart a connection
Body
platform"instagram" | "tiktok" | "facebook" | "youtube" | "x"Required- X returns
402 x_billing_requireduntil X is enabled for your account; see Usage. WhatsApp and Google Business Profile are accepted by the schema but return422 provider_not_supported. A business connects its WhatsApp number itself, through Embedded Signup on the dashboard's Accounts page. redirectUrlstring- Where to send the browser when authorization finishes. Its origin must be allowlisted on the server, and it must use HTTPS unless the host is localhost or 127.0.0.1. Omit it to poll only.
authMethod"instagram_login" | "facebook_login"- Instagram only, and optional.
instagram_login(the default) signs in on Instagram;facebook_loginsigns in with Facebook and connects the Instagram account linked to one of your customer's Pages. See Instagram login methods. Sending it with any other platform returns400 invalid_request.
curl --fail-with-body -X POST \
"$ADELI_URL/api/v1/profiles/$PROFILE_ID/connect" \
-H "Authorization: Bearer $ADELI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"platform":"tiktok","redirectUrl":"https://client.example/callback"}'A successful start returns 201:
{
"id": "00000000-0000-4000-8000-000000000002",
"profileId": "00000000-0000-4000-8000-000000000001",
"platform": "instagram",
"authMethod": "instagram_login",
"status": "pending_authorization",
"accountId": null,
"expiresAt": "2026-09-08T20:10:00.000Z",
"completedAt": null,
"error": null,
"authUrl": "https://www.instagram.com/oauth/authorize?..."
}Instagram sessions also carry the authMethod they were started with.
Errors — 401 unauthorized, 400 invalid_request,
400 invalid_redirect_uri, 404 profile_not_found,
409 auth_method_conflict, 422 provider_not_supported,
422 provider_not_configured, 502 database_error.
GET/api/v1/profiles/{profileId}/connect/{connectionSessionId}Poll a connection session
The same object without authUrl. Poll until status is terminal.
Session status
pending_authorization- Waiting for your customer to authorize. Becomes expired ten minutes after creation.
processing- The customer authorized and Adeli is exchanging the code with the provider. This is brief, and may finish after expiresAt has passed.
connectedterminal- Done.
accountIdis the connection to use in every later request. failedterminal- Authorization was denied or the exchange failed.
error.codesays why. expiredterminal- Nobody completed authorization in time. Start a new session.
{
"id": "00000000-0000-4000-8000-000000000002",
"profileId": "00000000-0000-4000-8000-000000000001",
"platform": "instagram",
"authMethod": "instagram_login",
"status": "connected",
"accountId": "00000000-0000-0000-0000-000000000000",
"expiresAt": "2026-09-08T20:10:00.000Z",
"completedAt": "2026-09-08T20:03:11.000Z",
"error": null
}Errors — 401 unauthorized, 400 invalid_request,
404 profile_not_found, 404 connection_session_not_found,
502 database_error.
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 X is enabled for your account,
starting a connect returns 402 x_billing_required before any authUrl exists.
See Usage.
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.
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.