*Social Media Manual — [index](../MANUAL.md)*

# 4. Connecting accounts

An **account** is one publishing destination: one Facebook Page, one Instagram business
account, one X user. The member who connects it owns it (chapter 3). There are two ways to
connect, and `social_get_status` says which a platform supports in its `connect_methods`
(`oauth`, `credential`, or both).

## Always start with `social_get_status`

Its `platforms[]` is everything you need to plan a connection:

| field | use |
|---|---|
| `platform` | the key to pass as `platform` (`facebook`, `instagram`, `x`) |
| `label` | human name (`Facebook Page`, `Instagram`, `X`) |
| `connect_methods` | `oauth` = browser sign-in via `social_start_connect`; `credential` = `social_connect_account` with a token |
| `credential_fields[]` | for the credential method: `name`, whether it is `secret`, and `help` text to show the user |
| `capabilities` | what posts can carry: `text`, `link`, `image`, `multi_image`, `video`, `edit`, `delete`, `schedule` (chapter 11; no live platform currently lists `video`) |
| `limits` | `text_max`, `media_min`, `media_max`, `mimes[]`, `media_bytes_max` |

A platform appears only when the operator has configured keys for it. If the user asks for a
platform that is not listed, say so; `PLATFORM_UNSUPPORTED` is what the tools answer.

## The browser flow (preferred)

1. `social_start_connect` with `platform` and an optional `label` (a nickname such as
   "Shop page"). Reply: `intent_id`, `authorize_url`, `expires_at` (30 minutes),
   `how_to_complete`.
2. Give the user the `authorize_url` and ask them to open it, sign in and approve. The
   platform sends them to the server's callback page, which finishes the code exchange (PKCE,
   state checked) and shows: *"Access granted. Return to your assistant and tell it to finish
   connecting."* No token is ever shown to the user or to you.
3. When the user says they are done, call `social_complete_connect` with the `intent_id`
   (or with no arguments: the caller's most recent approved connection is used).
   - `INTENT_PENDING` (`retryable: true`) means they have not approved yet; ask, then retry.
   - `INTENT_EXPIRED`: start again. `INTENT_FAILED`: the platform refused (details say why);
     start again. `INTENT_NOT_FOUND`: wrong id or someone else's intent.
4. The reply lists `connected[]` (each `account` plus `reconnected`, true when the same
   platform account was already yours and its credential was refreshed) and `skipped[]`
   (accounts the platform offered that could not be connected, with an error and message, for
   example a Page owned by another member).

What each platform offers at step 4:

- **Facebook**: every Page the signed-in user manages. One account per Page, each with its own
  long-lived Page token. Pass `external_ids` to connect a subset; omit to connect them all.
- **Instagram**: the Instagram business or creator accounts linked to those Pages. The Instagram
  account must be a professional account linked to a Facebook Page the user manages, or nothing
  is offered.
- **X**: the signed-in X user, one account.

Suggest a recovery code right after a first successful connect (chapter 2).

## Credential connect

`social_connect_account` with `platform`, `credential` (an object whose keys are the
`credential_fields` names from status) and an optional `label`. The credential is validated
with the platform, sealed at rest and never returned by any tool. Reply: `account`,
`reconnected`.

| platform | credential fields |
|---|---|
| `facebook` | `page_token`: a Page access token with `pages_manage_posts` |
| `instagram` | `page_token`: a Page access token for the Page linked to the Instagram account, with `instagram_content_publish` |
| `x` | `access_token` (OAuth 2.0 user token with `tweet.write`), optional `refresh_token` (lets the connection renew itself) |

Use this only when the user already has a token in hand; the browser flow is the normal path.
`CREDENTIAL_REQUIRED` means a field is missing; `CONNECT_FAILED` means the platform rejected
it (or was down, in which case `retryable` is true).

## Reconnecting

Connecting the same platform account again, by either method, refreshes its credential and
reports `reconnected: true`. Nothing else changes: the account id, its shares, drafts,
schedules and authorizations all stay.

An account whose credential has stopped working shows `health: reconnect_required` in
`social_list_accounts`, and platform calls on it answer `RECONNECT_REQUIRED`. Only the owner
can reconnect. Until they do, scheduled slots on that account fail at their time.

## Disconnecting

`social_disconnect_account` (owner): erases the credential, revokes every member's access
(`shares_revoked` says how many), and leaves the account in lists as disconnected. Drafts that
still target it fail at preview and publish with `ACCOUNT_DISCONNECTED` until they are edited.

## Meta app review and who can connect

The Facebook and Instagram connection goes through DataHandler's Meta app. Until Meta has
approved that app's permissions, Meta only lets people who hold a role on the app (admin,
developer or tester) complete the sign-in; anyone else gets an error on Meta's page, which
reaches you as `INTENT_FAILED`. If a user reports that Facebook refused them at the sign-in
step, that is the likely reason; the fix is on the operator's side, not in the tools.

## `social_list_accounts`

Everything the caller can use, with their `role` on each (`owner`, `editor`, `viewer`),
`platform`, `handle`, `label` and `health` (`ok`, `reconnect_required` or `disconnected`). Use the `account_id` values from here everywhere else.
