diff --git a/CLAUDE.md b/CLAUDE.md index 073731e..e342bbc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -48,6 +48,25 @@ Same paths, user configures `APP_URL` in `.env`: - Default instructions assume the user is on **TryPost Cloud** - Self-hosted specifics go in ``, ``, or the Self-Hosting anchor - Example: Platform pages say "click Connect" for cloud, API credentials in `` for self-hosted +- This includes `api-reference/` — those endpoints are documented against `https://app.trypost.it/api`, so describe the Cloud response + +**When a fixed behaviour becomes configurable**, keep the Cloud value as the statement and push the setting into self-hosting. Do not rewrite body prose into a two-mode comparison — it makes every reader parse a branch that only one of them is on. + +```mdx +{/* Wrong — the Cloud reader has to work out which half applies to them */} +On TryPost Cloud each workspace connects one account per network. On +self-hosted instances the cap is a setting; see ALLOW_MULTIPLE_SOCIAL_ACCOUNTS. + +{/* Right — Cloud is the statement, self-hosting is a pointer */} +Each workspace connects one account per network. + + + Self-hosting TryPost? This cap is a setting on your instance — see + [multiple accounts per network](/self-hosting/configuration#...). + +``` + +The same applies to anything a self-hoster can switch off or raise: platform availability, limits, AI providers. State what Cloud does; link out for the knob. ### URLs in examples - API curl examples: `https://app.trypost.it/api/...` diff --git a/api-reference/endpoint/list-social-accounts.mdx b/api-reference/endpoint/list-social-accounts.mdx index db5744f..1d9c88e 100644 --- a/api-reference/endpoint/list-social-accounts.mdx +++ b/api-reference/endpoint/list-social-accounts.mdx @@ -8,6 +8,12 @@ description: "List all connected social accounts for the current workspace, orde Plain array of social account objects. Each item has these fields: + + **Read accounts by `id`, not by `platform`.** A workspace holds [one account per network](/knowledge-base/social-accounts#connection-limits), so each `platform` appears at most once — but that is a plan limit, not a guarantee of this endpoint. + + +Accounts are connected from the dashboard, since every platform needs a browser to authorize. The API reads them and can toggle one [active or inactive](/api-reference/endpoint/toggle-social-account); it cannot add or remove one. + Social account UUID. diff --git a/docs.json b/docs.json index b6d6c6e..411c3f8 100644 --- a/docs.json +++ b/docs.json @@ -254,7 +254,8 @@ "pages": [ "self-hosting/configuration", "self-hosting/ai", - "self-hosting/production" + "self-hosting/production", + "self-hosting/upgrading" ] } ] diff --git a/knowledge-base/plans-and-billing.mdx b/knowledge-base/plans-and-billing.mdx index 444979a..f107422 100644 --- a/knowledge-base/plans-and-billing.mdx +++ b/knowledge-base/plans-and-billing.mdx @@ -26,6 +26,8 @@ Pricing is localized in some regions (for example, **R$60 / month** in Brazil). Each workspace connects one account on every supported network (Instagram, Facebook, LinkedIn, X, TikTok, YouTube, Pinterest, Threads, Bluesky, Mastodon, Telegram, Discord) and runs them from a single calendar. +A LinkedIn profile and a LinkedIn Page count as **one** network between them, as do Instagram Standalone and Instagram (Facebook Business) — see [connection limits](/knowledge-base/social-accounts#connection-limits). + To manage another brand or client, or a second account on the same network, add another workspace. Each workspace is billed at the same per-workspace price, and AI credits from every workspace are pooled across your account. ## Trial diff --git a/knowledge-base/social-accounts.mdx b/knowledge-base/social-accounts.mdx index aab2a9b..87df830 100644 --- a/knowledge-base/social-accounts.mdx +++ b/knowledge-base/social-accounts.mdx @@ -31,21 +31,33 @@ Social accounts are the connections between TryPost and your social media profil Go to **Accounts** in the dashboard and click the platform you want to connect. For most platforms, you'll be redirected to their authorization page. A few have extra steps: -- **LinkedIn Page** — after authorizing, pick which company page to connect +- **LinkedIn** — one card covers both your personal profile and the company pages you administer. After authorizing, pick which identity to connect - **Facebook** — after authorizing, pick which page to manage -- **Instagram (Standalone)** — uses Instagram's own OAuth flow; suitable for personal IG accounts -- **Instagram (Facebook Business)** — authorize through Facebook, then pick the Instagram account linked to a Facebook page (required for first-party publishing of feed/reels/stories/carousels) -- **YouTube** — authorize with Google, then pick the channel +- **Instagram** — one card offers both connection methods. **Standalone** uses Instagram's own OAuth (suitable for personal IG accounts); **Facebook Business** authorizes through Facebook and then picks the Instagram account linked to a page (required for first-party publishing of feed/reels/stories/carousels) +- **YouTube** — authorize with Google; the channel comes from the account you pick on Google's own screen - **Bluesky** — enter your handle and an [App Password](https://bsky.app/settings/app-passwords) (not your main password) - **Mastodon** — enter your instance URL first, then authorize on that instance - **Telegram** — add the TryPost bot as an admin to your channel or group, then post the `/connect` command shown in the dialog - **Discord** — authorize TryPost and choose which server to add the bot to (requires *Manage Server*); the whole server connects as one account and you pick the channel on each post -## Connection limits (Cloud only) + + Facebook and Instagram only show their page picker when there is something to choose between — administer a single page and TryPost connects it without asking. If the login has nothing to offer, it says so rather than showing an empty list. LinkedIn always shows its identity screen, even when your profile is the only thing on it. + -On TryPost Cloud, each workspace connects one account on every supported network (its social set). To connect a second account on the same network, add another workspace. +## Connection limits -Self-hosted instances have no cap. +Each workspace connects one account per network. To manage a second account on the same network, add another workspace. + +Two platforms count as **one network** when they are two ways into the same place, so they share that single slot: + +- LinkedIn profile and LinkedIn Page +- Instagram Standalone and Instagram (Facebook Business) + +Connecting one of a pair uses up the network's slot, so a workspace holds either a LinkedIn profile or a LinkedIn Page, not both. + + + Self-hosting TryPost? This cap is a setting on your instance — see [multiple accounts per network](/self-hosting/configuration#multiple-accounts-per-network). + ## Active vs. inactive @@ -83,6 +95,23 @@ If any of these detects a disconnected or expired account, the workspace owner r - **Permission revoked** — You manually revoked TryPost's access in the platform's settings. - **Platform policy** — The platform may revoke tokens for inactivity or policy changes. +## Reconnecting an account + +A card that needs attention shows a **Reconnect** button. Reconnecting refreshes that specific card — it keeps its posts, its scheduling history, and its place in your automations. It never creates a second card. + +Because it is tied to one card, you have to authorize **the same account** you are reconnecting: + +| What you'll see | What happened | What to do | +|---|---|---| +| *That is a different account. Authorize the one you are reconnecting.* | You signed in as someone else during the reconnect | Sign out of the platform, then reconnect and authorize the original account | +| *Page not found.* | The company page you are reconnecting is not administered by the login you used | Authorize with the account that administers that page | +| *This workspace already has an account for this network. Disconnect it first.* | The network's single slot is taken | Disconnect the existing account, or use another workspace | +| *Another connection is still finishing. Please try again in a moment.* | Two connections to the same network overlapped | Wait a moment and try again | + + + On Telegram, a reconnect must be confirmed **from the same channel or group** the card points at. Posting the `/connect` command somewhere else is rejected — and the code is not consumed, so you can still post it in the right place. + + ## Token security - Access tokens and refresh tokens are **encrypted at rest** using Laravel's encryption @@ -101,7 +130,7 @@ Go to **Accounts**, find the account, and click **Disconnect**. This: - No. Each social media account can only be connected once per workspace. If you try to connect an account that's already connected, TryPost will update the existing connection. + No. Each social media account is connected once per workspace, and its network's slot is taken while it is — so there is nothing to click to add it again. To refresh an account you already have, use [Reconnect](#reconnecting-an-account) on its card, which updates that card rather than creating another. Posts scheduled for a disconnected account will fail for that platform when the scheduled time arrives. Other platforms in the same post are not affected. You'll receive a notification about the failure. diff --git a/platforms/facebook.mdx b/platforms/facebook.mdx index 070318d..ada9d64 100644 --- a/platforms/facebook.mdx +++ b/platforms/facebook.mdx @@ -13,7 +13,7 @@ TryPost supports posting to Facebook pages. 1. Go to **Accounts** in the TryPost dashboard 2. Click **Connect Facebook** 3. Authorize TryPost on Facebook -4. Select the page you want to manage +4. Select the page you want to manage. If you administer only one, TryPost connects it without asking ## Supported content types diff --git a/platforms/instagram.mdx b/platforms/instagram.mdx index ac50776..ab5729d 100644 --- a/platforms/instagram.mdx +++ b/platforms/instagram.mdx @@ -13,23 +13,29 @@ TryPost supports two Instagram connection flavors, each backed by a different OA | **Instagram (Standalone)** | `instagram` | Personal Instagram accounts that aren't linked to a Facebook page. Uses Instagram's own OAuth. | | **Instagram (Facebook Business)** | `instagram-facebook` | Business or Creator accounts linked to a Facebook page. Required for first-party publishing of feed, reels, and stories at full quality. | -Connect either (or both) from **Accounts** in the dashboard. +Both are reached from the single **Instagram** card on the **Accounts** page, which asks which method you want. + + + The two count as **one network** and share a single slot, so a workspace connects one Instagram account by either method — not one of each. See [connection limits](/knowledge-base/social-accounts#connection-limits). + + The same Instagram account cannot be connected through both methods — authorizing one that is already connected the other way is refused rather than duplicated. + ## Connect your account ### Standalone (personal) 1. Go to **Accounts** in the TryPost dashboard -2. Click **Connect Instagram** +2. Click **Connect** on the **Instagram** card, then choose **Instagram** 3. Authorize TryPost on instagram.com 4. The account shows up immediately ### Business (via Facebook) 1. Go to **Accounts** in the TryPost dashboard -2. Click **Connect Instagram (Facebook Business)** +2. Click **Connect** on the **Instagram** card, then choose **Instagram (Facebook Business)** 3. Authorize TryPost through Facebook -4. Select the Facebook page whose Instagram account you want to connect +4. Select the Facebook page whose Instagram account you want to connect. If only one of your pages has one, TryPost connects it without asking ## Supported content types diff --git a/platforms/linkedin.mdx b/platforms/linkedin.mdx index b841aaf..42d00b4 100644 --- a/platforms/linkedin.mdx +++ b/platforms/linkedin.mdx @@ -10,18 +10,20 @@ TryPost supports posting to LinkedIn personal profiles and company pages. ## Connect your account -### Personal profile +A single **LinkedIn** card covers both your personal profile and the company pages you administer — there is no separate "Connect LinkedIn Page" button. 1. Go to **Accounts** in the TryPost dashboard -2. Click **Connect LinkedIn** -3. Authorize TryPost on LinkedIn — your profile is connected directly, no extra picker needed +2. Click **Connect** on **LinkedIn** +3. Authorize TryPost on LinkedIn +4. Pick what to connect on the **Select a LinkedIn identity** screen: your personal profile, or one of the Pages you administer -### Company page +Each identity connects as its own social account. -1. Go to **Accounts** and click **Connect LinkedIn Page** -2. Authorize TryPost on LinkedIn with organization admin scope -3. TryPost loads every Page you administer and redirects you to a dedicated **Select a LinkedIn Page** screen -4. Pick the Page you want to schedule for. Each Page connects as its own social account — repeat the flow to add more. + + Profile and Page count as **one network** and share a single slot, so a workspace connects one or the other, not both. See [connection limits](/knowledge-base/social-accounts#connection-limits). + + Connecting a second identity means authorizing again, which is normal — LinkedIn keeps both tokens valid as long as TryPost asks for the same permissions each time. + ## Supported content types diff --git a/platforms/telegram.mdx b/platforms/telegram.mdx index 8fb08a7..c2fd1bf 100644 --- a/platforms/telegram.mdx +++ b/platforms/telegram.mdx @@ -20,7 +20,13 @@ TryPost publishes to Telegram **channels and groups** through a shared TryPost b The bot must be an **administrator** of the channel. Telegram only delivers channel posts (including the `/connect` command and reactions) to bots that are admins, and the bot needs admin rights to publish. -You can connect as many channels and groups as you like — each one becomes its own account. +Each channel or group becomes its own account, and a workspace connects one of them. To run a second channel, add another workspace — see [connection limits](/knowledge-base/social-accounts#connection-limits). + +## Reconnecting a channel + +A channel that lost its connection shows a **Reconnect** button, which issues a fresh `/connect` code. Post that code **in the same channel the card points at** — a reconnect is tied to one card and will not move it to a different chat. + +Posting it somewhere else is rejected with *Post the command in the channel you are reconnecting*. The code is **not** consumed, so you can still post the same one in the right channel. ## Supported content types diff --git a/platforms/youtube.mdx b/platforms/youtube.mdx index 8996a9e..d20604c 100644 --- a/platforms/youtube.mdx +++ b/platforms/youtube.mdx @@ -12,8 +12,8 @@ TryPost supports uploading YouTube Shorts. 1. Go to **Accounts** in the TryPost dashboard 2. Click **Connect YouTube** -3. Authorize TryPost with your Google account -4. Select the YouTube channel to connect +3. Authorize TryPost with your Google account, choosing the channel on Google's own screen +4. The channel shows up immediately — TryPost connects the one that authorization was granted for ## Supported content types diff --git a/self-hosting/configuration.mdx b/self-hosting/configuration.mdx index 21cafaf..5e5b8f7 100644 --- a/self-hosting/configuration.mdx +++ b/self-hosting/configuration.mdx @@ -42,6 +42,30 @@ SELF_HOSTED=true There's no reason to flip this to `false` on a self-hosted install. The Cloud SaaS deployment is the only place that runs with billing enabled. +## Multiple accounts per network + +```env +ALLOW_MULTIPLE_SOCIAL_ACCOUNTS=true +``` + +Controls whether a workspace can connect more than one account on the same social network — two LinkedIns, two Instagrams, and so on. `.env.example` and the Docker compose file ship with it set to `true`. + +Two flavours of the same platform count as **one network**, so they share the slot: + +- LinkedIn profile + LinkedIn Page +- Instagram Standalone + Instagram (Facebook Business) + +| Value | Behaviour | +|---|---| +| `true` | A workspace may connect several accounts on the same network. Each network's tile gains a **Connect another** card | +| `false` | One account per network. A second one is refused with *This workspace already has an account for this network* | + + + When the variable is unset it falls back to `SELF_HOSTED`, so instances that upgrade without touching `.env` keep allowing multiple accounts. This is deliberately independent of `SELF_HOSTED` so it can be flipped on its own. + + +The same account is never connected twice, whatever the value — reconnecting an identity that already has a card updates that card instead of adding another, including across the two flavours of one network. + ## Database TryPost supports PostgreSQL and MySQL. @@ -111,6 +135,8 @@ Alternatively, set `PASSPORT_PRIVATE_KEY` and `PASSPORT_PUBLIC_KEY` in `.env` (P MCP clients authenticate with OAuth (`mcp:use`), not with an API key Bearer header. API keys are for the REST API only. +The [API reference](/api-reference/introduction) describes your instance too — same routes, same payloads, same MCP tools. Only two things differ: the base URL is your `APP_URL` followed by `/api`, and responses reflect the settings on this page rather than the Cloud plan. The most visible one is [multiple accounts per network](#multiple-accounts-per-network) — where it is on, `GET /social-accounts` can return several accounts sharing one `platform` value, so read it by `id`. + Full install order: [Installation](/self-hosting/installation). diff --git a/self-hosting/docker.mdx b/self-hosting/docker.mdx index 96e434e..9c3ac1c 100644 --- a/self-hosting/docker.mdx +++ b/self-hosting/docker.mdx @@ -159,6 +159,10 @@ docker compose -f compose.prod.yaml exec app php artisan migrate --force docker compose -f compose.prod.yaml down ``` + + Running `migrate --force` to move an existing instance to a newer version? Back the database up first and check [Upgrading](/self-hosting/upgrading) — some releases rewrite data as well as schema. + + --- ## Local development diff --git a/self-hosting/installation.mdx b/self-hosting/installation.mdx index 539916a..9a5bae1 100644 --- a/self-hosting/installation.mdx +++ b/self-hosting/installation.mdx @@ -86,6 +86,10 @@ Also configure **mail** (invites / password reset) and optional social OAuth / A php artisan migrate --force ``` + + On a fresh install this just builds the schema. When you run it later to **upgrade** an existing instance, back the database up first and check [Upgrading](/self-hosting/upgrading) — some releases rewrite data as well as schema. + + ### 5. Passport keys (and local storage symlink) Passport needs an RSA key pair on disk to **sign and verify** API / MCP tokens: diff --git a/self-hosting/upgrading.mdx b/self-hosting/upgrading.mdx new file mode 100644 index 0000000..92dceb6 --- /dev/null +++ b/self-hosting/upgrading.mdx @@ -0,0 +1,55 @@ +--- +title: "Upgrading" +description: "What to check before pulling a new version of TryPost" +--- + +## The routine + +Pull the new version, then run the same steps you already run on a deploy: + +- Rebuild dependencies and assets, and re-run the [optimization commands](/self-hosting/production#optimizations) +- Apply migrations with `php artisan migrate --force` +- Restart the queue workers so they pick up the new code — `php artisan horizon:terminate` lets Supervisor bring them back + +On Docker, [Common production commands](/self-hosting/docker#common-production-commands) has the container equivalents. + + + **Back the database up before `migrate --force`.** Migrations run unattended and some of them rewrite data, not just schema. A dump you can restore is the only reliable way back. + + +## Release notes that need attention + +Most upgrades need nothing beyond the routine above. The ones below change data or defaults, so read them if you are crossing that version. + +### Multiple social accounts per network + +This release adds a database constraint that a workspace cannot hold the same identity twice on the same platform. + +**Why it matters:** earlier versions could store the same account more than once — the per-network guard was skipped on self-hosted installs, and Pinterest created a fresh row on every connect. Duplicates like that would block the new constraint, so the migration merges them first. + +For each duplicated identity it keeps the **most recently created** account, moves that account's scheduled posts and automation targets onto the survivor, and deletes the rest. + +Merging can leave a post aimed at the same account twice, which would publish it twice. Those repeats are collapsed: + +- If the post has **not** published there yet, one target survives — the one you had enabled, if any +- If it **has** already published there, every unpublished repeat is removed, since the content has gone out + +Targets that already published are never deleted or altered. They record what went out, and hold the id the platform needs to manage that post later. + +**This is one-way.** Rolling the migration back drops the constraint but does not bring the merged rows back. + +Every merge is written to your log at `warning` level with enough detail to reconstruct it: + +``` +Collapsed duplicate social accounts + workspace_id, platform, platform_user_id, + kept_id, dropped_ids, + post_platforms_repointed, post_platforms_deleted, + automations_rewritten +``` + + + Want to know what it will do before it does it? Restore a copy of your production database somewhere disposable, run `php artisan migrate --force` against the copy, and read those log lines. If nothing is logged, you have no duplicates and the migration is a no-op. + + +**New setting:** [`ALLOW_MULTIPLE_SOCIAL_ACCOUNTS`](/self-hosting/configuration#multiple-accounts-per-network) decides whether a workspace may connect several accounts on one network. You do not have to set it — when it is missing it follows `SELF_HOSTED`, so an existing self-hosted install keeps the behaviour it had.