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

# 11. Platforms

What `social_get_status` reports per platform, with the behaviour behind it. Only platforms
the operator has configured are listed by the server; this chapter describes the ones the
connector knows how to drive.

## Summary

| platform | label | connect | capabilities | text_max | media | per-file |
|---|---|---|---|---|---|---|
| `facebook` | Facebook Page | `oauth`, `credential` (`page_token`) | text, link, image, multi_image, edit, delete, schedule | 63 206 | 0..10, JPEG/PNG/GIF/WebP | 10 MB |
| `instagram` | Instagram | `oauth` (through the Facebook sign-in), `credential` (`page_token`) | image, multi_image, delete | 2 200 | 1..10, JPEG only | 8 MB |
| `x` | X | `oauth`, `credential` (`access_token`, optional `refresh_token`) | text, link, image, multi_image, delete | 280 weighted | 0..4, JPEG/PNG/GIF/WebP | 5 MB |
| `fake` | test platform | `oauth`, `credential` (`access_token` starting `fake-`) | text, link, image, multi_image, edit, delete, schedule | 100 | 0..2 | test only |

No platform currently lists `video`, so a draft with a video answers `CAPABILITY_UNSUPPORTED`
at preview.

## Facebook

- One account per **Page**. The browser flow offers every Page the signed-in user manages;
  each gets its own long-lived Page token.
- Text posts, link posts, one photo, a multi-photo post; `edit` changes the message of a live
  post; `delete` removes it.
- `schedule` in capabilities means the platform could schedule natively; the connector
  schedules on its own side regardless, so this changes nothing for you.
- Sign-in goes through Facebook Login for Business with the app's configured permissions
  (Pages, Page content, business management, Instagram basic and content publishing).

## Instagram

- Reached **through the Facebook Page link**: the browser flow is the Facebook sign-in, and
  the accounts offered are the Instagram business or creator accounts linked to the user's
  Pages. An Instagram account not linked to a Page cannot be connected.
- Image posts only: one JPEG, or a carousel of 2 to 10 JPEGs. No text-only posts
  (`MEDIA_REQUIRED`), no PNG (`MEDIA_UNSUPPORTED`), no links as attachments, at most 30
  hashtags (`TOO_MANY_HASHTAGS`), caption up to 2 200 characters.
- Publishing is a two-step container-then-publish flow with status polling on the connector
  side; it can take longer than a Facebook post.
- No edits (`CAPABILITY_UNSUPPORTED`); delete is supported.

## X

- The browser flow connects the signed-in user (OAuth 2.1 with PKCE and `offline.access`).
- Tokens last two hours. Before every call the connector refreshes with the rotating refresh
  token and seals the new pair back onto the account, so the user never has to reconnect for
  expiry alone. A refresh that fails is `RECONNECT_REQUIRED`.
- 280 weighted characters (every URL counts as 23, the rest by character), up to 4 images;
  the preview's `rendered.characters` is the weighted count.
- No edits; delete is supported.
- X charges per request. Its calls are billable in the usage ledger (chapter 10). If the
  operator's X account has no credit, publishing fails with `PLATFORM_REJECTED` or
  `RATE_LIMITED` until credit is added; connecting still works.

## The test platform

`fake` exists only on test servers. It accepts any `access_token` starting with `fake-`
(except `fake-dead`), and markers in the post text script the outcome: `[fail]`, `[rate]`,
`[down]`, `[unknown]`. It deduplicates by idempotency key, so reconciliation works there and
nowhere else. If you see it listed on a production server, tell the operator.

## How platform errors reach you

| platform answer | code | retryable |
|---|---|---|
| 401 / expired or revoked token | `RECONNECT_REQUIRED` | no; owner reconnects |
| 429 / rate limit | `RATE_LIMITED` | yes, after the platform's hint or 15 minutes |
| 5xx / connection failure | `PLATFORM_UNAVAILABLE` | yes |
| no reply at all | `PUBLICATION_OUTCOME_UNKNOWN` | reconciled where possible (chapter 7) |
| anything else | `PLATFORM_REJECTED` | no; the message carries the platform's reason |

These appear as `error.code` on direct calls (connect, read back, edit, delete) and as
`error_code` on publication attempts.
