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

  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.

POST/api/v1/profiles/{profileId}/connectStart a connection

Body

platform"instagram" | "tiktok" | "facebook" | "youtube" | "x"Required
X returns 402 x_billing_required until X is enabled for your account; see Usage. WhatsApp and Google Business Profile are accepted by the schema but return 422 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_login signs 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 returns 400 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:

json
{
  "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. accountId is the connection to use in every later request.
failedterminal
Authorization was denied or the exchange failed. error.code says why.
expiredterminal
Nobody completed authorization in time. Start a new session.
json
{
  "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-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 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.