quorlasocial/docs/platforms.md
metatroncubeswdev fc2179b343 Track platform quotas; wait and retry instead of failing
- quota.js: per-platform limits (YouTube 10,000 units/day Pacific per project,
  Instagram 100 / Threads 250 / TikTok 15 posts per 24 h per account, ...),
  usage log, .env overrides (QUOTA_<PLATFORM>)
- Publishing checks the budget first and records usage; when a platform is
  out of quota (or answers quota exceeded / 429) the post stays approved and
  the queue retries after the reset, without re-posting finished platforms
- Drafts that exceed a day's limit get a warning
- Connections page shows used/limit and when it frees up
- Tests for windows (incl. DST), shared vs per-account limits and the
  wait-and-retry queue flow; docs for quotas and the Google app setup

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 21:56:54 -04:00

89 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Other platforms: LinkedIn, Google Business Profile, YouTube, TikTok, Pinterest, Threads, Reddit
Facebook and Instagram still come through the agency's Meta system user. Every other platform is connected
**per client** on the **Connections** page: an admin signs in to the client's account once ("Connect"), and
from then on posts to that platform go through Publishing with the same draft → approve → publish flow.
## How it fits together
| Piece | Where |
|---|---|
| Connections page (set up apps, connect/disconnect accounts, choose company Page / location) | `dashboard/web/src/pages/Connections.jsx` |
| "Post to" picker with each platform's fields, used in New post and library Schedule | `dashboard/web/src/PlatformPicker.jsx` |
| API routes, OAuth callback `/oauth/<platform>/callback`, media links `/m/<token>/<file>` | `dashboard/server/index.js` |
| Encrypted store for app keys and tokens (`data/connections.json`, AES-256-GCM) | `connector/src/platforms/vault.js` |
| Sign-in (OAuth 2, PKCE for Google), token refresh | `connector/src/platforms/oauth.js`, `index.js` |
| One adapter per platform | `connector/src/platforms/<platform>.js` |
| Queue integration (validation, publishing) | `connector/src/schedule.js` |
| Agent tools: `list_connections`; `schedule_post` / `schedule_from_library` accept all platforms + `options` | `connector/src/server.js` |
| Offline tests (simulated platform APIs) | `connector/test/platforms.test.mjs` → `node --test connector/test/platforms.test.mjs` |
Tokens and app secrets are encrypted with a key derived from `DASHBOARD_SECRET` (or `QUORLA_SECRET_KEY`).
They are never sent to the browser. If that secret changes, every platform shows "Reconnect needed".
## What each platform needs before it works for real
Every platform requires a developer app owned by the agency, with the Redirect URI shown in its **Set up** dialog
(`https://app.quorlasocial.com/oauth/<platform>/callback`), plus the privacy policy and terms pages on quorlasocial.com.
| Platform | Posts | Developer app | Approval needed for real use | Until approved |
|---|---|---|---|---|
| LinkedIn | text, image, multi-image, video; as a person or company Page | linkedin.com/developers | **Community Management API** (company Pages, analytics) | Personal profiles only: set Scopes to `openid profile w_member_social` |
| Google Business Profile | update with photo and button | Google Cloud (OAuth client) | **Business Profile API access** form + OAuth consent verification | API calls return quota errors; test users only |
| YouTube | video (Shorts when vertical, under 3 min) | Google Cloud (same OAuth client) | **YouTube API audit** + consent verification | Uploads are forced to Private; small daily quota |
| TikTok | video, photo / photo carousel | developers.tiktok.com | **App review** for Content Posting API (Direct Post) | Posts are "Only me"; sandbox users only |
| Pinterest | image, multi-image (2–5), video Pins on a board | developers.pinterest.com | **Standard access** (demo video) | Trial: set `PINTEREST_SANDBOX=true`, Pins go to the sandbox |
| Threads | text, image, video, carousel | Meta for Developers (Threads use case) | **Meta app review** for Threads permissions | Only accounts added as Threads testers |
| Reddit | text or link post to a subreddit | reddit.com/prefs/apps (web app) | Reddit's approval for commercial API use | Works for the agency's own account; check each subreddit's rules |
Platforms that fetch media from a link (TikTok photos, Threads, Google Business Profile) get a signed link to the
file that expires after 6 hours. On the server this uses `PUBLIC_URL`, or `https://APP_DOMAIN` when that's set.
TikTok also needs the domain verified in its developer portal.
## What was tested, and what wasn't
- **Tested offline (15 tests, all passing):** encryption at rest, sign-in links for every platform (client ID
parameter, scopes, PKCE, offline access), the callback state check, token refresh and "reconnect needed",
and each platform's publish requests (LinkedIn image to a company Page with hashtags, YouTube resumable upload,
GBP post with button and photo link, TikTok video chunks and photo pull, Pinterest base64 Pin, Threads long-lived
token and image post, Reddit text post and error handling), plus format and field validation.
- **Tested against the local dashboard (21 checks):** admin-only setup and connect, secrets never returned,
sign-in redirect and callback errors (forged state, cancelled, signed out), media links (served, tampered
refused), drafts to connected and unconnected platforms, per-platform captions, disconnect.
- **Not tested:** real calls to each platform. Each platform needs its developer app and keys first. The request
formats follow each platform's documentation as of this build, but expect small fixes the first time each one
connects and posts. Test with the agency's own accounts before connecting clients.
- **Not built yet:** comments/inbox and analytics for these platforms (only follower counts are shown),
Reddit image posts, Pinterest video cover images, LinkedIn document (PDF) posts, X (paid API).
## Quotas and limits
Quorla tracks each platform's limit (`connector/src/platforms/quota.js`, usage in `data/quota.json`):
| Platform | Limit tracked | Window |
|---|---|---|
| YouTube | 10,000 units per Google project, shared by all clients (upload = 1,600, read = 1) → about 6 uploads/day until the YouTube audit raises it | calendar day, Pacific time |
| Instagram | 100 API posts per account | rolling 24 h |
| Facebook | 200 posts per Page | rolling 24 h |
| Threads | 250 posts per profile | rolling 24 h |
| TikTok | 15 posts per creator | rolling 24 h |
| LinkedIn | 100 posts per member/Page | rolling 24 h |
| Pinterest | 100 Pins per account | rolling 24 h |
| Google Business Profile | 50 updates per account | rolling 24 h |
| Reddit | 10 posts per account (kept low: subreddits ban frequent posters) | rolling 24 h |
- Before publishing, Quorla checks the budget. If it's used up, or the platform answers "quota exceeded" / "too many
requests", the post **stays approved and waits**; the queue retries it automatically after the reset. Platforms that
already went out are not posted twice.
- Saving a draft that would exceed a day's limit shows a warning on the draft (not an error).
- The Connections page shows used / limit and when it frees up.
- Raise a limit after a platform approves more with `QUOTA_<PLATFORM>=n` in `.env` (e.g. `QUOTA_YOUTUBE=1000000`).
- Tests: `node --test connector/test/quota.test.mjs`.
## Suggested order after merging
1. Deploy (`git pull && docker compose up -d --build`), then Connections → Set up for one platform at a time.
2. Start with **LinkedIn** (personal-profile scopes) and **Google Business Profile**, using Metatroncube's own accounts.
3. Apply for the approvals in the table above. They take days to weeks, which is why they should start early.
4. Connect client accounts once each platform is approved.