# 05 — Integration Capability Matrix

> ⚠ **Every entry below must be re-verified against the provider's current official documentation and app-review rules at implementation time.** Provider APIs, scopes, tiers, and prices change frequently. This matrix is seeded into `provider_capabilities` as **versioned data** (`config/social/capabilities/{provider}.v{n}.json`), not hardcoded in the UI.

## 0. Implementation status (M2, 30 Oct 2026)

- All 9 adapters are implemented for connect, discovery, refresh, revoke and health (`app/Domain/Social/Providers`), with error normalisation and contract tests.
- The matrix below is seeded as data (`config/social/capabilities/*.json`, version 1). Admins can switch any action per platform (Admin → Integrations → Capabilities).
- Every provider is **configurable** until credentials are entered, and **blocked** until the integration is enabled with review status *approved* or *not required*. Nothing is simulated outside local/testing.
- Effective per-account states also include `missing_scopes` and `account_unhealthy`.
- Setup and callback URLs: [docs/dev/social.md](../dev/social.md).

## 1. Status vocabulary

| Status | Meaning in product | UI behaviour |
|--------|--------------------|--------------|
| `available` | Credentials configured, app approved for the scope, feature flag on | Selectable |
| `configurable` | Supported by the official API but needs platform-admin setup (credentials, app review, or paid tier) before it works | Hidden from agencies, or shown greyed out "Coming soon", per flag |
| `blocked` | Waiting on an external approval or business decision (app review, partner program, paid tier) | Shown greyed out with the reason. **Never simulated** |
| `unavailable` | No official API surface for this action | Not shown, or shown as "Not supported by {platform}" |

**Local/testing environments** use `FakeProvider` adapters that return deterministic results, and only when `APP_ENV ∈ {local, testing}` **and** `SOCIAL_FAKE_PROVIDERS=true`. A boot-time guard throws if fakes are enabled in staging or production.

## 2. Adapter interfaces

```php
interface SocialProvider {
    public function key(): string;                                  // 'facebook', 'instagram', ...
    public function authorizationUrl(OAuthState $state): string;    // state + PKCE where supported
    public function exchangeCode(string $code, OAuthState $state): TokenSet;
    public function discoverAccounts(TokenSet $tokens): array;      // DiscoveredAccount[]
    public function refresh(SocialAccountToken $token): TokenSet;   // throws TokenRevoked / RefreshUnsupported
    public function revoke(SocialAccountToken $token): void;
    public function healthCheck(SocialAccount $account): HealthResult; // validates token + scopes
}
interface PublishesContent {
    public function validate(PublishPayload $payload, SocialAccount $a): ValidationResult;
    public function publish(PublishPayload $payload, SocialAccount $a, string $idempotencyKey): PublishResult;
    public function findExistingPublication(PublishAttempt $attempt): ?PublishResult; // reconciliation, null if unsupported
}
interface ReadsInsights    { public function accountMetrics(...); public function postMetrics(...); }
interface HandlesInbox     { public function fetchThreads(...); public function reply(...); }
interface VerifiesWebhooks { public function verify(Request $r): bool; public function parse(Request $r): array; }
```

Errors are normalised into `ProviderException` subclasses: `TransientError`, `RateLimited(retryAfter)`, `AuthError` (→ needs_reconnect), `PermissionError` (scope missing), `ValidationError` (permanent), `UnknownOutcome` (timeout after send).

## 3. Provider matrix (planned connectors)

| Provider | Account types | OAuth | Key scopes / products (verify) | Publish capabilities | Insights | Inbox | Main gates / caveats | Initial status |
|----------|---------------|-------|--------------------------------|----------------------|----------|-------|----------------------|----------------|
| **Facebook** | Page | Facebook Login (OAuth 2.0, state). Long-lived user token → Page tokens | `pages_show_list`, `pages_manage_posts`, `pages_read_engagement`, `read_insights`, `pages_manage_metadata`; inbox: `pages_messaging`, `pages_manage_engagement`; Business Manager: `business_management` | Text, link, photo(s), video, scheduled native (optional), Reels on Pages | Page and post insights | Page comments, Messenger (24 h window rules) | Meta App Review + **Business Verification**. User must have a Page task (role) | `blocked` → until Meta app approved |
| **Instagram** | Professional (Business/Creator) | Via Facebook Login (IG linked to Page) **or** Instagram API with Instagram Login | `instagram_basic`, `instagram_content_publish`, `instagram_manage_insights`, `instagram_manage_comments`, `instagram_manage_messages` (or `instagram_business_*` equivalents) | Image, carousel, Reels, Stories (verify), via container → publish. Media must be at a public HTTPS URL (signed S3 URL with sufficient TTL) | Account + media insights | Comments; DMs need Advanced Access | Personal accounts are unsupported. Rolling publish limit per 24 h (check `content_publishing_limit`). App Review | `blocked` |
| **Threads** | Threads profile | Threads OAuth | `threads_basic`, `threads_content_publish`, `threads_manage_insights`, `threads_manage_replies` (verify) | Text, image, video, carousel (container flow) | Post/profile insights | Replies management | Meta App Review | `blocked` |
| **LinkedIn** | Member, Organization page | OAuth 2.0 (3-legged), state | Member: `openid profile w_member_social` (Share on LinkedIn, self-serve). Org: `w_organization_social`, `r_organization_social`, `rw_organization_admin` (**Community Management API**, application required) | Posts API (`/rest/posts`, versioned header): text, image, video, document, multi-image | Org page/share statistics (Community Mgmt) | Org comments (Community Mgmt) | Org posting needs an admin role on the page **and** Community Management API approval. Refresh tokens only for approved apps | Member: `configurable`; Org: `blocked` |
| **YouTube** | Channel | Google OAuth 2.0 + PKCE, offline access | `youtube.upload`, `youtube.readonly`, `yt-analytics.readonly`, `youtube.force-ssl` (comments) | Video upload (resumable), title/description/tags/privacy, `publishAt` scheduling (private → public), custom thumbnail (verified channels) | YouTube Analytics API | Comment threads (read/reply/moderate) | **Quota** (default 10,000 units/day per project; uploads are expensive). Unverified/unaudited projects: uploads are forced private. Google OAuth verification for sensitive scopes | `configurable` (needs Google verification + audit) |
| **X (Twitter)** | User | OAuth 2.0 Authorization Code + **PKCE** | `tweet.read`, `tweet.write`, `users.read`, `media.write`, `offline.access` | Text, images, video, threads (v2 posts + media upload endpoints) | Public/non-public metrics per access tier | DMs (`dm.read`, `dm.write`) are restricted/paid | **Paid access tiers / pay-per-use; limits and pricing change.** Business decision required | `blocked` (pending tier decision) |
| **TikTok** | Creator account | TikTok Login Kit OAuth 2.0 + PKCE | `user.info.basic`, `video.upload`, `video.publish` | Content Posting API: Direct Post or upload-to-inbox (draft). Must show creator info, privacy options, and consent UI per TikTok UX guidelines | Display API basic video stats (verify) | Not available broadly | **Audit required for Direct Post. Unaudited clients can only post private (SELF_ONLY).** Strict UX rules: no automatic publishing without user-chosen settings | `blocked` (audit) |
| **Pinterest** | Business/personal | OAuth 2.0 | `boards:read`, `pins:read`, `pins:write`, `user_accounts:read` | Create Pin (image, video, link) on a chosen board | Pin/account analytics | — (unavailable) | Trial access has limits. Standard access requires review | `configurable` |
| **Google Business Profile** | Location | Google OAuth 2.0 | `business.manage` | Local posts (what's new, event, offer) via the v4 `localPosts` API | Business Profile Performance API | Reviews: read and reply | **GBP API access must be requested and approved.** The location must be verified | `blocked` (API access request) |

## 4. Inbox availability summary (M6)

| Provider | Comments | Direct messages | Reviews |
|----------|----------|-----------------|---------|
| Facebook Page | `configurable` | `configurable` (Messenger policy, 24 h window) | `unavailable` (Page recommendations: verify) |
| Instagram | `configurable` | `blocked` (Advanced Access) | — |
| Threads | `configurable` (replies) | `unavailable` | — |
| LinkedIn Org | `blocked` (Community Mgmt) | `unavailable` | — |
| YouTube | `configurable` | `unavailable` | — |
| X | `blocked` (tier) | `blocked` (tier) | — |
| TikTok | `unavailable` (verify) | `unavailable` | — |
| Pinterest | `unavailable` | `unavailable` | — |
| GBP | — | `unavailable` | `blocked` (API access) |

## 5. Webhooks / deauthorisation

| Provider | Webhook | Verification |
|----------|---------|--------------|
| Meta (FB/IG/Threads) | Page/IG subscriptions (feed, comments, messages); **deauthorize** and **data-deletion** callbacks (mandatory for app review) | `X-Hub-Signature-256` HMAC with the app secret; GET verify-token handshake |
| YouTube | PubSubHubbub (video feed) — optional | Hub challenge |
| TikTok | Post status webhooks (verify) | Signature header (verify) |
| Razorpay | Subscription/payment events | `X-Razorpay-Signature` HMAC |

All webhooks: verify the signature → insert into `webhook_events` with UQ(provider, external_event_id) (duplicates are dropped) → `200` fast → process on the `webhooks` queue. Ordering is handled by comparing provider timestamps and per-entity versions.

## 6. Production setup checklist per provider (done by the platform admin)

1. Create a developer app under the WhizzAct business account (not a personal account). Enable 2FA on the developer account.
2. Register redirect URIs: `{APP_URL}/oauth/{provider}/callback` (one central callback; the tenant is restored from `oauth_states`).
3. Add privacy policy, terms, data-deletion URL, and app icon. Complete business verification where required.
4. Request the scopes listed above and record screencasts for review using the staging environment.
5. Store the client ID/secret in Secrets Manager. Create the `platform_integrations` row with `review_status`.
6. Flip the capability rows from `blocked` to `available` only after approval. Turn on the Pennant flag `provider.{key}`.

## 7. Quota & rate-limit awareness

- `RateLimiterRegistry` buckets: `provider:{key}:app`, `provider:{key}:account:{id}`, `tenant:{id}:publishing`.
- Adapters parse quota headers (Meta `X-App-Usage` / `X-Business-Use-Case-Usage`, X `x-rate-limit-*`, LinkedIn 429 + `Retry-After`) and back off proactively at 80 % usage.
- YouTube: a daily unit budget is tracked in Redis per project. The composer warns when the projected units for scheduled uploads exceed the remaining budget.
- Quota dashboards live in super admin (M6).
