@sprid/cli 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,55 +52,55 @@ export const GUIDES = [
52
52
  "id": "you-need",
53
53
  "title": "You need",
54
54
  "kind": "requirements",
55
- "markdown": "- The email address that should own the accounts, access to its inbox, and your phone for verification.\n- Your chosen account names, handles and the purpose of each account. Handles are proposals until the platform confirms them.\n- Access to your existing Meta business portfolio if it should own the Facebook Pages and Instagram accounts.\n\nKeep passwords, verification codes and identity documents in the platform’s own browser or app. An agent can prepare forms and continue after you sign in."
55
+ "markdown": "- The email address that should own the accounts, access to its inbox, and your phone for verification.\n- The display name, desired handle and purpose of each account. A handle is only a proposal until the platform accepts it.\n- Access to your Meta business portfolio, if it should own the Facebook Pages and Instagram accounts.\n\nPasswords, verification codes and identity documents stay in the platform’s own browser or app. An agent can prepare forms and continue after you sign in."
56
56
  },
57
57
  {
58
58
  "id": "set-up-your-account-map",
59
59
  "title": "Set up your account map",
60
60
  "kind": "steps",
61
- "markdown": "You can connect one main account or several accounts with different purposes. A side account might share educational content, follow a character or cover a topic separately from your main brand.\n\n| Account purpose | Example display name | Example handle | Sprid publishing account |\n|---|---|---|---|\n| Main brand or product | Your Brand | yourbrand | yourbrand |\n| Founder or creator | Alex from Your Brand | alexfromyourbrand | brand-founder |\n| Character or persona | Everyday Alex | everydayalex | everyday-alex |\n| Topic or community | Small Space Ideas | smallspaceideas | small-space-ideas |\n| Campaign or project | The Weekend Project | theweekendproject | weekend-project |\n| Language or regional audience | Your Brand Español | yourbrand.espanol | brand-spanish |\n\nThese describe what you publish. They are separate from platform settings such as Instagram’s Business or Creator account type, or Google’s Brand Account. A character account can represent a fictional persona; keep that identity clear in its profile.\n\nChoose the accounts that serve your audience. You do not need every type or every platform. For each identity, write down the owner, purpose, desired handle and platforms. Add language or region only when relevant. The examples are naming ideas, not availability checks.\n\nGroup matching profiles across platforms under one Sprid publishing account. For example, your main brand’s Instagram and YouTube can share a destination, while a founder’s account has its own. This keeps content and connections attached to the identity that will publish them.\n\nUse the same owner email and existing business portfolio where appropriate. An Instagram login, a Facebook Page, a Meta business portfolio and a Sprid publishing account are separate objects. Connecting a channel to Sprid does not transfer its ownership to a portfolio.\n\nYour agent can inspect `sprid apps` and reuse existing publishing accounts. Missing destinations can be created with `sprid account create --file account.json`; see the [CLI reference](https://sprid.studio/docs/cli). A Sprid destination does not reserve a handle or create a social account."
61
+ "markdown": "Connect one main account, or several with different purposes. Choose only the accounts and platforms your audience needs.\n\n| Account purpose | Example display name | Example handle | Sprid publishing account |\n|---|---|---|---|\n| Main brand or product | Your Brand | yourbrand | yourbrand |\n| Founder or creator | Alex from Your Brand | alexfromyourbrand | brand-founder |\n| Character or persona | Everyday Alex | everydayalex | everyday-alex |\n| Topic or community | Small Space Ideas | smallspaceideas | small-space-ideas |\n| Campaign or project | The Weekend Project | theweekendproject | weekend-project |\n| Language or regional audience | Your Brand Español | yourbrand.espanol | brand-spanish |\n\nThe examples are naming ideas, not availability checks. The purpose is separate from platform settings such as Instagram’s Business or Creator type or Google’s Brand Account. A character account can represent a fictional persona; keep that clear in its profile.\n\nFor each identity, write down the owner, purpose, handle and platforms. Group the same identity’s profiles under one Sprid publishing account: your brand’s Instagram and YouTube share one, a founder’s account gets its own.\n\nAn Instagram login, a Facebook Page, a Meta business portfolio and a Sprid publishing account are separate objects. Connecting a channel to Sprid does not move its ownership into a portfolio, and a Sprid destination does not reserve a handle or create a social account.\n\nYour agent can list existing publishing accounts with `sprid apps` and create missing ones with `sprid account create --file account.json` (see the [CLI reference](https://sprid.studio/docs/cli))."
62
62
  },
63
63
  {
64
64
  "id": "set-up-instagram",
65
65
  "title": "Set up Instagram",
66
66
  "kind": "steps",
67
- "markdown": "1. Open [Instagram signup](https://www.instagram.com/accounts/emailsignup/). Reuse an existing account if it already serves the intended purpose.\n2. Enter the owner email, display name and desired handle. Complete the password, birthday and any verification in Instagram itself.\n3. Check the profile’s actual handle and account contact email. Do not assume the requested handle was accepted.\n4. Use a Business or Creator account. Choose Business for a business or brand, or Creator for a creator-led account. Select a category that describes the account.\n5. If you use a Meta business portfolio, have its administrator add the Instagram account there and verify who has control. This is separate from Sprid authorization.\n6. Follow the [Instagram connection guide](https://sprid.studio/docs/connect/instagram), one account at a time.\n\nThe browser connection may offer professional-account conversion directly: **Change → Business → Next → category → Done → Continue**. These labels were observed in September 2026 and may vary. If creating an additional account takes you back to login, finish signup in a separate browser session or the Instagram app; keep the working account signed in."
67
+ "markdown": "1. Open [Instagram signup](https://www.instagram.com/accounts/emailsignup/), or reuse an existing account that already serves the purpose.\n2. Enter the owner email, display name and desired handle. Complete password, birthday and verification in Instagram.\n3. Check the profile’s actual handle and contact email.\n4. Switch to a professional account: **Business** for a brand, **Creator** for a creator-led account, with a matching category.\n5. If a Meta business portfolio should own it, its administrator adds the account there. This is separate from Sprid authorization.\n6. Follow the [Instagram connection guide](https://sprid.studio/docs/connect/instagram), one account at a time.\n\nThe Sprid connection may offer the professional switch itself: **Change → Business → Next → category → Done → Continue** (labels as of September 2026). If signing up an additional account sends you back to login, finish signup in a separate browser session or the Instagram app and keep the working account signed in."
68
68
  },
69
69
  {
70
70
  "id": "set-up-facebook",
71
71
  "title": "Set up Facebook",
72
72
  "kind": "steps",
73
- "markdown": "Reuse any existing Page before creating another. For a new Page, open [Create a Page](https://www.facebook.com/pages/create/) under the profile that should manage it. Use the chosen public display name. If a business portfolio should own the Page, have its administrator add or claim it there.\n\nFollow the [Facebook connection guide](https://sprid.studio/docs/connect/facebook). If your profile manages several Pages, choose the specific Page returned by the connection flow. The Facebook profile’s name is not the Page destination."
73
+ "markdown": "Reuse an existing Page where you can. Otherwise open [Create a Page](https://www.facebook.com/pages/create/) under the profile that should manage it, using the public display name. If a business portfolio should own the Page, its administrator adds or claims it there. Then follow the [Facebook connection guide](https://sprid.studio/docs/connect/facebook)."
74
74
  },
75
75
  {
76
76
  "id": "set-up-youtube",
77
77
  "title": "Set up YouTube",
78
78
  "kind": "steps",
79
- "markdown": "Sign in to [YouTube’s channel list](https://www.youtube.com/channel_switcher) with the intended owner’s Google account. Review existing channels, then choose **Create a channel** for each additional identity or audience you want to publish as. Check both the channel name and handle.\n\nIf YouTube asks for advanced-feature verification, the account owner completes the offered verification in YouTube Studio. A submitted video or ID can remain under review. Continue other platforms while approval is pending, then recheck eligibility before retrying channel creation. Do not assume an existing channel’s approval has unlocked every new channel. [YouTube explains eligibility and owner verification](https://support.google.com/youtube/answer/9891124?hl=en).\n\nOnce the channel exists, follow the [YouTube connection guide](https://sprid.studio/docs/connect/youtube) and choose that exact channel in Google’s authorization screen."
79
+ "markdown": "Sign in to [YouTube’s channel list](https://www.youtube.com/channel_switcher) with the owner’s Google account. Review existing channels, then choose **Create a channel** for each additional identity. Check both the channel name and handle.\n\nIf YouTube asks for advanced-feature verification, the owner completes it in YouTube Studio (video, ID or channel history). Review can stay pending: continue other platforms and recheck eligibility before retrying. Approval on one channel does not unlock every new channel. See [YouTube eligibility and owner verification](https://support.google.com/youtube/answer/9891124?hl=en).\n\nThen follow the [YouTube connection guide](https://sprid.studio/docs/connect/youtube) and pick that exact channel in Google’s authorization screen."
80
80
  },
81
81
  {
82
82
  "id": "set-up-tiktok",
83
83
  "title": "Set up TikTok",
84
84
  "kind": "steps",
85
- "markdown": "Create or sign in to each intended account in TikTok. Confirm the handle and owner’s recovery access, then follow the [TikTok connection guide](https://sprid.studio/docs/connect/tiktok). Complete any verification yourself. The agent can resume the connection afterward."
85
+ "markdown": "Create or sign in to each account in TikTok, confirm the handle and the owner’s recovery access, and complete any verification yourself. Then follow the [TikTok connection guide](https://sprid.studio/docs/connect/tiktok)."
86
86
  },
87
87
  {
88
88
  "id": "then-run",
89
89
  "title": "Then run",
90
90
  "kind": "command",
91
- "markdown": "```sh\nsprid connect instagram --account yourbrand\n```\n\nRepeat with the actual Sprid publishing-account slug and platform: `instagram`, `facebook`, `youtube` or `tiktok`. Connect only accounts that already exist.\n\nIf your agent uses a separate browser session, it can run the command with `--no-browser` and open the returned authorization link in the session signed in to the correct account. Keep that temporary link private. This avoids opening another account’s login in your default browser."
91
+ "markdown": "```sh\nsprid connect instagram --account yourbrand\n```\n\nUse your Sprid publishing-account slug and the platform: `instagram`, `facebook`, `youtube` or `tiktok`. Connect only accounts that already exist.\n\nIn an agent-controlled browser, add `--no-browser` and open the returned authorization link in the session signed in to the correct account. Keep that link private."
92
92
  },
93
93
  {
94
94
  "id": "how-to-check-it-worked",
95
95
  "title": "How to check it worked",
96
96
  "kind": "verify",
97
- "markdown": "Run `sprid status --json` and match the returned platform identity to the intended Sprid publishing account. Record these separately for each platform:\n\n- Account created, with its confirmed handle or Page/channel ID.\n- Owner and business-portfolio access checked.\n- Any provider verification or tester invitation still pending.\n- Sprid connection saved for the correct destination.\n- Publishing tested with explicitly approved content, or still untested.\n\nAn authorization screen saying you allowed access is not proof that Sprid saved a connection. A connected channel is not proof that a post was delivered. Check the platform itself when you authorize a publishing test."
97
+ "markdown": "Run `sprid status --json` and match each platform identity to its Sprid publishing account. Track per platform:\n\n- Account created, with its confirmed handle or Page/channel ID.\n- Owner and business-portfolio access checked.\n- Provider verification or tester invitation still pending.\n- Sprid connection saved to the correct destination.\n- Publishing tested with approved content, or untested.\n\n“You allowed access” on the authorization screen does not prove Sprid saved the connection, and a connected channel does not prove a post was delivered. Check the platform itself after a publishing test."
98
98
  },
99
99
  {
100
100
  "id": "if-it-fails",
101
101
  "title": "If it fails",
102
102
  "kind": "troubleshooting",
103
- "markdown": "- **Another account appears in authorization:** cancel. Sign in to the intended account and restart the connection. Do not move a working channel just to clear the error.\n- **Several Facebook Pages are listed:** rerun with `--page <id>` using the intended Page’s returned ID.\n- **YouTube verification is pending:** leave its destination pending and continue another platform.\n- **Instagram authorization succeeds but Sprid reports a token-exchange error:** save the error message and contact [Sprid support](mailto:hello@sprid.studio). Sprid must check its Meta permissions and any tester restrictions. You do not need to create your own developer app. A tester invitation, if required, comes from Sprid and must be accepted by the intended Instagram account.\n- **The CLI points to an unavailable local server:** inspect `sprid whoami --json`. For production, use a command-scoped `SPRID_URL=https://api.sprid.studio` and retry. Do not change credentials or another workspace to solve a server-address mismatch."
103
+ "markdown": "- **Another account appears in authorization:** cancel, sign in to the intended account and restart. Do not move a working channel to clear the error.\n- **Several Facebook Pages are listed:** rerun with `--page <id>` using the intended Page’s returned ID.\n- **YouTube verification is pending:** leave that destination pending and continue with another platform.\n- **Instagram authorizes but Sprid reports a token-exchange error:** send the error to [Sprid support](mailto:hello@sprid.studio). Sprid checks its Meta permissions and tester restrictions; any tester invitation comes from Sprid and must be accepted by the intended Instagram account. You do not need your own developer app.\n- **The CLI points to an unavailable local server:** check `sprid whoami --json`, then retry with `SPRID_URL=https://api.sprid.studio` for that command. Do not change credentials or workspaces to fix a server-address mismatch."
104
104
  },
105
105
  {
106
106
  "id": "sources",
@@ -109,8 +109,8 @@ export const GUIDES = [
109
109
  "markdown": "- [Instagram signup](https://www.instagram.com/accounts/emailsignup/)\n- [Instagram Business Login](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/business-login/)\n- [Meta app modes](https://developers.facebook.com/docs/development/build-and-test/app-modes)\n- [Meta Accounts Center](https://www.facebook.com/help/943858526073065)\n- [Create a Facebook Page](https://www.facebook.com/pages/create/)\n- [YouTube channel list](https://www.youtube.com/channel_switcher)\n- [YouTube feature eligibility](https://support.google.com/youtube/answer/9891124?hl=en)"
110
110
  }
111
111
  ],
112
- "markdown": "# Create your social accounts\n\n**What Sprid does with this:** Connect your brand, creator and other social accounts to their own publishing destinations, so approved content reaches the intended audience.\n\n## You need\n\n- The email address that should own the accounts, access to its inbox, and your phone for verification.\n- Your chosen account names, handles and the purpose of each account. Handles are proposals until the platform confirms them.\n- Access to your existing Meta business portfolio if it should own the Facebook Pages and Instagram accounts.\n\nKeep passwords, verification codes and identity documents in the platform’s own browser or app. An agent can prepare forms and continue after you sign in.\n\n## Set up your account map\n\nYou can connect one main account or several accounts with different purposes. A side account might share educational content, follow a character or cover a topic separately from your main brand.\n\n| Account purpose | Example display name | Example handle | Sprid publishing account |\n|---|---|---|---|\n| Main brand or product | Your Brand | yourbrand | yourbrand |\n| Founder or creator | Alex from Your Brand | alexfromyourbrand | brand-founder |\n| Character or persona | Everyday Alex | everydayalex | everyday-alex |\n| Topic or community | Small Space Ideas | smallspaceideas | small-space-ideas |\n| Campaign or project | The Weekend Project | theweekendproject | weekend-project |\n| Language or regional audience | Your Brand Español | yourbrand.espanol | brand-spanish |\n\nThese describe what you publish. They are separate from platform settings such as Instagram’s Business or Creator account type, or Google’s Brand Account. A character account can represent a fictional persona; keep that identity clear in its profile.\n\nChoose the accounts that serve your audience. You do not need every type or every platform. For each identity, write down the owner, purpose, desired handle and platforms. Add language or region only when relevant. The examples are naming ideas, not availability checks.\n\nGroup matching profiles across platforms under one Sprid publishing account. For example, your main brand’s Instagram and YouTube can share a destination, while a founder’s account has its own. This keeps content and connections attached to the identity that will publish them.\n\nUse the same owner email and existing business portfolio where appropriate. An Instagram login, a Facebook Page, a Meta business portfolio and a Sprid publishing account are separate objects. Connecting a channel to Sprid does not transfer its ownership to a portfolio.\n\nYour agent can inspect `sprid apps` and reuse existing publishing accounts. Missing destinations can be created with `sprid account create --file account.json`; see the [CLI reference](https://sprid.studio/docs/cli). A Sprid destination does not reserve a handle or create a social account.\n\n## Set up Instagram\n\n1. Open [Instagram signup](https://www.instagram.com/accounts/emailsignup/). Reuse an existing account if it already serves the intended purpose.\n2. Enter the owner email, display name and desired handle. Complete the password, birthday and any verification in Instagram itself.\n3. Check the profile’s actual handle and account contact email. Do not assume the requested handle was accepted.\n4. Use a Business or Creator account. Choose Business for a business or brand, or Creator for a creator-led account. Select a category that describes the account.\n5. If you use a Meta business portfolio, have its administrator add the Instagram account there and verify who has control. This is separate from Sprid authorization.\n6. Follow the [Instagram connection guide](https://sprid.studio/docs/connect/instagram), one account at a time.\n\nThe browser connection may offer professional-account conversion directly: **Change → Business → Next → category → Done → Continue**. These labels were observed in September 2026 and may vary. If creating an additional account takes you back to login, finish signup in a separate browser session or the Instagram app; keep the working account signed in.\n\n## Set up Facebook\n\nReuse any existing Page before creating another. For a new Page, open [Create a Page](https://www.facebook.com/pages/create/) under the profile that should manage it. Use the chosen public display name. If a business portfolio should own the Page, have its administrator add or claim it there.\n\nFollow the [Facebook connection guide](https://sprid.studio/docs/connect/facebook). If your profile manages several Pages, choose the specific Page returned by the connection flow. The Facebook profile’s name is not the Page destination.\n\n## Set up YouTube\n\nSign in to [YouTube’s channel list](https://www.youtube.com/channel_switcher) with the intended owner’s Google account. Review existing channels, then choose **Create a channel** for each additional identity or audience you want to publish as. Check both the channel name and handle.\n\nIf YouTube asks for advanced-feature verification, the account owner completes the offered verification in YouTube Studio. A submitted video or ID can remain under review. Continue other platforms while approval is pending, then recheck eligibility before retrying channel creation. Do not assume an existing channel’s approval has unlocked every new channel. [YouTube explains eligibility and owner verification](https://support.google.com/youtube/answer/9891124?hl=en).\n\nOnce the channel exists, follow the [YouTube connection guide](https://sprid.studio/docs/connect/youtube) and choose that exact channel in Google’s authorization screen.\n\n## Set up TikTok\n\nCreate or sign in to each intended account in TikTok. Confirm the handle and owner’s recovery access, then follow the [TikTok connection guide](https://sprid.studio/docs/connect/tiktok). Complete any verification yourself. The agent can resume the connection afterward.\n\n## Then run\n\n```sh\nsprid connect instagram --account yourbrand\n```\n\nRepeat with the actual Sprid publishing-account slug and platform: `instagram`, `facebook`, `youtube` or `tiktok`. Connect only accounts that already exist.\n\nIf your agent uses a separate browser session, it can run the command with `--no-browser` and open the returned authorization link in the session signed in to the correct account. Keep that temporary link private. This avoids opening another account’s login in your default browser.\n\n## How to check it worked\n\nRun `sprid status --json` and match the returned platform identity to the intended Sprid publishing account. Record these separately for each platform:\n\n- Account created, with its confirmed handle or Page/channel ID.\n- Owner and business-portfolio access checked.\n- Any provider verification or tester invitation still pending.\n- Sprid connection saved for the correct destination.\n- Publishing tested with explicitly approved content, or still untested.\n\nAn authorization screen saying you allowed access is not proof that Sprid saved a connection. A connected channel is not proof that a post was delivered. Check the platform itself when you authorize a publishing test.\n\n## If it fails\n\n- **Another account appears in authorization:** cancel. Sign in to the intended account and restart the connection. Do not move a working channel just to clear the error.\n- **Several Facebook Pages are listed:** rerun with `--page <id>` using the intended Page’s returned ID.\n- **YouTube verification is pending:** leave its destination pending and continue another platform.\n- **Instagram authorization succeeds but Sprid reports a token-exchange error:** save the error message and contact [Sprid support](mailto:hello@sprid.studio). Sprid must check its Meta permissions and any tester restrictions. You do not need to create your own developer app. A tester invitation, if required, comes from Sprid and must be accepted by the intended Instagram account.\n- **The CLI points to an unavailable local server:** inspect `sprid whoami --json`. For production, use a command-scoped `SPRID_URL=https://api.sprid.studio` and retry. Do not change credentials or another workspace to solve a server-address mismatch.\n\n## Sources\n\n- [Instagram signup](https://www.instagram.com/accounts/emailsignup/)\n- [Instagram Business Login](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/business-login/)\n- [Meta app modes](https://developers.facebook.com/docs/development/build-and-test/app-modes)\n- [Meta Accounts Center](https://www.facebook.com/help/943858526073065)\n- [Create a Facebook Page](https://www.facebook.com/pages/create/)\n- [YouTube channel list](https://www.youtube.com/channel_switcher)\n- [YouTube feature eligibility](https://support.google.com/youtube/answer/9891124?hl=en)\n",
113
- "revision": "037b74ff55bb32a8"
112
+ "markdown": "# Create your social accounts\n\n**What Sprid does with this:** Connect your brand, creator and other social accounts to their own publishing destinations, so approved content reaches the intended audience.\n\n## You need\n\n- The email address that should own the accounts, access to its inbox, and your phone for verification.\n- The display name, desired handle and purpose of each account. A handle is only a proposal until the platform accepts it.\n- Access to your Meta business portfolio, if it should own the Facebook Pages and Instagram accounts.\n\nPasswords, verification codes and identity documents stay in the platform’s own browser or app. An agent can prepare forms and continue after you sign in.\n\n## Set up your account map\n\nConnect one main account, or several with different purposes. Choose only the accounts and platforms your audience needs.\n\n| Account purpose | Example display name | Example handle | Sprid publishing account |\n|---|---|---|---|\n| Main brand or product | Your Brand | yourbrand | yourbrand |\n| Founder or creator | Alex from Your Brand | alexfromyourbrand | brand-founder |\n| Character or persona | Everyday Alex | everydayalex | everyday-alex |\n| Topic or community | Small Space Ideas | smallspaceideas | small-space-ideas |\n| Campaign or project | The Weekend Project | theweekendproject | weekend-project |\n| Language or regional audience | Your Brand Español | yourbrand.espanol | brand-spanish |\n\nThe examples are naming ideas, not availability checks. The purpose is separate from platform settings such as Instagram’s Business or Creator type or Google’s Brand Account. A character account can represent a fictional persona; keep that clear in its profile.\n\nFor each identity, write down the owner, purpose, handle and platforms. Group the same identity’s profiles under one Sprid publishing account: your brand’s Instagram and YouTube share one, a founder’s account gets its own.\n\nAn Instagram login, a Facebook Page, a Meta business portfolio and a Sprid publishing account are separate objects. Connecting a channel to Sprid does not move its ownership into a portfolio, and a Sprid destination does not reserve a handle or create a social account.\n\nYour agent can list existing publishing accounts with `sprid apps` and create missing ones with `sprid account create --file account.json` (see the [CLI reference](https://sprid.studio/docs/cli)).\n\n## Set up Instagram\n\n1. Open [Instagram signup](https://www.instagram.com/accounts/emailsignup/), or reuse an existing account that already serves the purpose.\n2. Enter the owner email, display name and desired handle. Complete password, birthday and verification in Instagram.\n3. Check the profile’s actual handle and contact email.\n4. Switch to a professional account: **Business** for a brand, **Creator** for a creator-led account, with a matching category.\n5. If a Meta business portfolio should own it, its administrator adds the account there. This is separate from Sprid authorization.\n6. Follow the [Instagram connection guide](https://sprid.studio/docs/connect/instagram), one account at a time.\n\nThe Sprid connection may offer the professional switch itself: **Change → Business → Next → category → Done → Continue** (labels as of September 2026). If signing up an additional account sends you back to login, finish signup in a separate browser session or the Instagram app and keep the working account signed in.\n\n## Set up Facebook\n\nReuse an existing Page where you can. Otherwise open [Create a Page](https://www.facebook.com/pages/create/) under the profile that should manage it, using the public display name. If a business portfolio should own the Page, its administrator adds or claims it there. Then follow the [Facebook connection guide](https://sprid.studio/docs/connect/facebook).\n\n## Set up YouTube\n\nSign in to [YouTube’s channel list](https://www.youtube.com/channel_switcher) with the owner’s Google account. Review existing channels, then choose **Create a channel** for each additional identity. Check both the channel name and handle.\n\nIf YouTube asks for advanced-feature verification, the owner completes it in YouTube Studio (video, ID or channel history). Review can stay pending: continue other platforms and recheck eligibility before retrying. Approval on one channel does not unlock every new channel. See [YouTube eligibility and owner verification](https://support.google.com/youtube/answer/9891124?hl=en).\n\nThen follow the [YouTube connection guide](https://sprid.studio/docs/connect/youtube) and pick that exact channel in Google’s authorization screen.\n\n## Set up TikTok\n\nCreate or sign in to each account in TikTok, confirm the handle and the owner’s recovery access, and complete any verification yourself. Then follow the [TikTok connection guide](https://sprid.studio/docs/connect/tiktok).\n\n## Then run\n\n```sh\nsprid connect instagram --account yourbrand\n```\n\nUse your Sprid publishing-account slug and the platform: `instagram`, `facebook`, `youtube` or `tiktok`. Connect only accounts that already exist.\n\nIn an agent-controlled browser, add `--no-browser` and open the returned authorization link in the session signed in to the correct account. Keep that link private.\n\n## How to check it worked\n\nRun `sprid status --json` and match each platform identity to its Sprid publishing account. Track per platform:\n\n- Account created, with its confirmed handle or Page/channel ID.\n- Owner and business-portfolio access checked.\n- Provider verification or tester invitation still pending.\n- Sprid connection saved to the correct destination.\n- Publishing tested with approved content, or untested.\n\n“You allowed access” on the authorization screen does not prove Sprid saved the connection, and a connected channel does not prove a post was delivered. Check the platform itself after a publishing test.\n\n## If it fails\n\n- **Another account appears in authorization:** cancel, sign in to the intended account and restart. Do not move a working channel to clear the error.\n- **Several Facebook Pages are listed:** rerun with `--page <id>` using the intended Page’s returned ID.\n- **YouTube verification is pending:** leave that destination pending and continue with another platform.\n- **Instagram authorizes but Sprid reports a token-exchange error:** send the error to [Sprid support](mailto:hello@sprid.studio). Sprid checks its Meta permissions and tester restrictions; any tester invitation comes from Sprid and must be accepted by the intended Instagram account. You do not need your own developer app.\n- **The CLI points to an unavailable local server:** check `sprid whoami --json`, then retry with `SPRID_URL=https://api.sprid.studio` for that command. Do not change credentials or workspaces to fix a server-address mismatch.\n\n## Sources\n\n- [Instagram signup](https://www.instagram.com/accounts/emailsignup/)\n- [Instagram Business Login](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/business-login/)\n- [Meta app modes](https://developers.facebook.com/docs/development/build-and-test/app-modes)\n- [Meta Accounts Center](https://www.facebook.com/help/943858526073065)\n- [Create a Facebook Page](https://www.facebook.com/pages/create/)\n- [YouTube channel list](https://www.youtube.com/channel_switcher)\n- [YouTube feature eligibility](https://support.google.com/youtube/answer/9891124?hl=en)\n",
113
+ "revision": "fc2505d0782288a4"
114
114
  },
115
115
  {
116
116
  "id": "app-store-connect",
@@ -123,53 +123,53 @@ export const GUIDES = [
123
123
  "id": "you-need",
124
124
  "title": "You need",
125
125
  "kind": "requirements",
126
- "markdown": "**Account Holder** or **Admin** access to your Apple Developer team. You will create a **Team API key** with **Admin** access; it covers every app on the account.\n\nIf you only want to read reviews and edit metadata, **App Manager** access is sufficient. Use Admin for review replies and analytics reports."
126
+ "markdown": "**Account Holder** or **Admin** access to your Apple Developer team, to create a **Team API key** with **Admin** access. One key covers every app on the account.\n\n**App Manager** is enough to read reviews and edit metadata. Review replies and analytics reports need Admin."
127
127
  },
128
128
  {
129
129
  "id": "click-path-appstoreconnectapplecom-checked-2026-09-08",
130
130
  "title": "Click path (appstoreconnect.apple.com, checked 2026-09-08)",
131
131
  "kind": "steps",
132
- "markdown": "1. Open [App Store Connect](https://appstoreconnect.apple.com) → **Users and Access → Integrations → App Store Connect API → Team Keys**.\n2. Click **Generate API Key**, or **+** beside **Active**.\n3. Name the key `Sprid`, choose **Admin** access, then click **Generate**.\n4. Click **Download API Key** and keep the `.p8` file. Apple offers this download once; if lost, revoke the key and create another.\n5. Copy the **Key ID** from its row and the **Issuer ID** above the table. Use these in the command below."
132
+ "markdown": "1. Open [App Store Connect](https://appstoreconnect.apple.com) → **Users and Access → Integrations → App Store Connect API → Team Keys**.\n2. Click **Generate API Key**, or **+** beside **Active**.\n3. Name it `Sprid`, choose **Admin**, then **Generate**.\n4. Click **Download API Key** and keep the `.p8` file. Apple offers the download once; if you lose it, revoke the key and make another.\n5. Copy the **Key ID** from its row and the **Issuer ID** above the table."
133
133
  },
134
134
  {
135
135
  "id": "find-the-apps-numeric-id",
136
136
  "title": "Find the app's numeric id",
137
137
  "kind": "identifiers",
138
- "markdown": "Open **Apps → your app → General → App Information → General Information → Apple ID**. If your Sprid app profile is missing this number, run `sprid init` to add it."
138
+ "markdown": "**Apps → your app → General → App Information → General Information → Apple ID**. If your Sprid app profile lacks it, run `sprid init`."
139
139
  },
140
140
  {
141
141
  "id": "then-run",
142
142
  "title": "Then run",
143
143
  "kind": "command",
144
- "markdown": "```\nsprid connect asc --key ~/Downloads/AuthKey_XXXXXXXXXX.p8 --key-id XXXXXXXXXX --issuer YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY\n```\n\nReplace the file path and ids with yours. Keep the key file private; do not paste its contents into chat."
144
+ "markdown": "```\nsprid connect asc --key ~/Downloads/AuthKey_XXXXXXXXXX.p8 --key-id XXXXXXXXXX --issuer YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY\n```\n\nUse your own file path and ids. Keep the key file out of chat."
145
145
  },
146
146
  {
147
147
  "id": "how-to-check-it-worked",
148
148
  "title": "How to check it worked",
149
149
  "kind": "verify",
150
- "markdown": "Run `sprid status` to check setup. Then ask your agent: “Check that Sprid can read this app’s App Store reviews and analytics. Tell me if any reports are still missing.”\n\nA saved key alone does not confirm access. The daily review email only arrives when there are new reviews."
150
+ "markdown": "Run `sprid status`, then ask your agent: “Check that Sprid can read this app’s App Store reviews and analytics. Tell me if any reports are still missing.” A saved key alone does not confirm access. The daily review email only arrives when there are new reviews."
151
151
  },
152
152
  {
153
153
  "id": "if-it-fails",
154
154
  "title": "If it fails",
155
155
  "kind": "troubleshooting",
156
- "markdown": "- **Key not accepted:** check the Key ID matches the downloaded file and the Issuer ID came from above the keys table. A Team ID will not work here.\n- **Permission denied:** create a replacement key with **Admin** access. An existing key’s role cannot be changed.\n- **Older analytics are missing:** Apple starts preparing reports after setup. Missing history does not mean downloads fell to zero."
156
+ "markdown": "- **Key not accepted:** check the Key ID matches the downloaded file and the Issuer ID came from above the keys table. A Team ID will not work.\n- **Permission denied:** create a replacement key with **Admin** access. A key’s role cannot be changed.\n- **Older analytics are missing:** Apple starts preparing reports after setup. Missing history does not mean downloads fell to zero."
157
+ },
158
+ {
159
+ "id": "investigate-with-this-connection",
160
+ "title": "Investigate with this connection",
161
+ "kind": "detail",
162
+ "markdown": "Reads `reviews`, `versions`, `reports` and `report_rows` for the pinned app. Read-only: no report request is created.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source asc --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
157
163
  },
158
164
  {
159
165
  "id": "sources",
160
166
  "title": "Sources",
161
167
  "kind": "sources",
162
168
  "markdown": "- [Generate keys](https://developer.apple.com/help/app-store-connect/get-started/app-store-connect-api)\n- [Role permissions](https://developer.apple.com/help/app-store-connect/reference/account-management/role-permissions)\n- [Respond to reviews](https://developer.apple.com/help/app-store-connect/monitor-ratings-and-reviews/respond-to-reviews)\n- [API key access levels compared](https://aso.dev/app-store-connect/api-key-access-levels/)\n- [App Manager 403 on review replies](https://developer.apple.com/forums/thread/800545)"
163
- },
164
- {
165
- "id": "investigate-with-this-connection",
166
- "title": "Investigate with this connection",
167
- "kind": "detail",
168
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `reviews`, `versions`, `reports`, `report_rows`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source asc --json\n```\n\nPinned app. Reviews and existing analytics report definitions are read-only. No report request is created. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification."
169
169
  }
170
170
  ],
171
- "markdown": "# App Store Connect\n\n**What Sprid does with this:** Read App Store reviews and download reports, and update listing copy when you approve it.\n\n## You need\n\n**Account Holder** or **Admin** access to your Apple Developer team. You will create a **Team API key** with **Admin** access; it covers every app on the account.\n\nIf you only want to read reviews and edit metadata, **App Manager** access is sufficient. Use Admin for review replies and analytics reports.\n\n## Click path (appstoreconnect.apple.com, checked 2026-09-08)\n\n1. Open [App Store Connect](https://appstoreconnect.apple.com) → **Users and Access → Integrations → App Store Connect API → Team Keys**.\n2. Click **Generate API Key**, or **+** beside **Active**.\n3. Name the key `Sprid`, choose **Admin** access, then click **Generate**.\n4. Click **Download API Key** and keep the `.p8` file. Apple offers this download once; if lost, revoke the key and create another.\n5. Copy the **Key ID** from its row and the **Issuer ID** above the table. Use these in the command below.\n\n## Find the app's numeric id\n\nOpen **Apps → your app → General → App Information → General Information → Apple ID**. If your Sprid app profile is missing this number, run `sprid init` to add it.\n\n## Then run\n\n```\nsprid connect asc --key ~/Downloads/AuthKey_XXXXXXXXXX.p8 --key-id XXXXXXXXXX --issuer YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY\n```\n\nReplace the file path and ids with yours. Keep the key file private; do not paste its contents into chat.\n\n## How to check it worked\n\nRun `sprid status` to check setup. Then ask your agent: “Check that Sprid can read this app’s App Store reviews and analytics. Tell me if any reports are still missing.”\n\nA saved key alone does not confirm access. The daily review email only arrives when there are new reviews.\n\n## If it fails\n\n- **Key not accepted:** check the Key ID matches the downloaded file and the Issuer ID came from above the keys table. A Team ID will not work here.\n- **Permission denied:** create a replacement key with **Admin** access. An existing key’s role cannot be changed.\n- **Older analytics are missing:** Apple starts preparing reports after setup. Missing history does not mean downloads fell to zero.\n\n## Sources\n\n- [Generate keys](https://developer.apple.com/help/app-store-connect/get-started/app-store-connect-api)\n- [Role permissions](https://developer.apple.com/help/app-store-connect/reference/account-management/role-permissions)\n- [Respond to reviews](https://developer.apple.com/help/app-store-connect/monitor-ratings-and-reviews/respond-to-reviews)\n- [API key access levels compared](https://aso.dev/app-store-connect/api-key-access-levels/)\n- [App Manager 403 on review replies](https://developer.apple.com/forums/thread/800545)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `reviews`, `versions`, `reports`, `report_rows`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source asc --json\n```\n\nPinned app. Reviews and existing analytics report definitions are read-only. No report request is created. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n",
172
- "revision": "788c2f1980b1bd27"
171
+ "markdown": "# App Store Connect\n\n**What Sprid does with this:** Read App Store reviews and download reports, and update listing copy when you approve it.\n\n## You need\n\n**Account Holder** or **Admin** access to your Apple Developer team, to create a **Team API key** with **Admin** access. One key covers every app on the account.\n\n**App Manager** is enough to read reviews and edit metadata. Review replies and analytics reports need Admin.\n\n## Click path (appstoreconnect.apple.com, checked 2026-09-08)\n\n1. Open [App Store Connect](https://appstoreconnect.apple.com) → **Users and Access → Integrations → App Store Connect API → Team Keys**.\n2. Click **Generate API Key**, or **+** beside **Active**.\n3. Name it `Sprid`, choose **Admin**, then **Generate**.\n4. Click **Download API Key** and keep the `.p8` file. Apple offers the download once; if you lose it, revoke the key and make another.\n5. Copy the **Key ID** from its row and the **Issuer ID** above the table.\n\n## Find the app's numeric id\n\n**Apps → your app → General → App Information → General Information → Apple ID**. If your Sprid app profile lacks it, run `sprid init`.\n\n## Then run\n\n```\nsprid connect asc --key ~/Downloads/AuthKey_XXXXXXXXXX.p8 --key-id XXXXXXXXXX --issuer YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY\n```\n\nUse your own file path and ids. Keep the key file out of chat.\n\n## How to check it worked\n\nRun `sprid status`, then ask your agent: “Check that Sprid can read this app’s App Store reviews and analytics. Tell me if any reports are still missing.” A saved key alone does not confirm access. The daily review email only arrives when there are new reviews.\n\n## If it fails\n\n- **Key not accepted:** check the Key ID matches the downloaded file and the Issuer ID came from above the keys table. A Team ID will not work.\n- **Permission denied:** create a replacement key with **Admin** access. A key’s role cannot be changed.\n- **Older analytics are missing:** Apple starts preparing reports after setup. Missing history does not mean downloads fell to zero.\n\n## Investigate with this connection\n\nReads `reviews`, `versions`, `reports` and `report_rows` for the pinned app. Read-only: no report request is created.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source asc --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources\n\n- [Generate keys](https://developer.apple.com/help/app-store-connect/get-started/app-store-connect-api)\n- [Role permissions](https://developer.apple.com/help/app-store-connect/reference/account-management/role-permissions)\n- [Respond to reviews](https://developer.apple.com/help/app-store-connect/monitor-ratings-and-reviews/respond-to-reviews)\n- [API key access levels compared](https://aso.dev/app-store-connect/api-key-access-levels/)\n- [App Manager 403 on review replies](https://developer.apple.com/forums/thread/800545)\n",
172
+ "revision": "b20748bb9f9756c8"
173
173
  },
174
174
  {
175
175
  "id": "google-play",
@@ -182,53 +182,53 @@ export const GUIDES = [
182
182
  "id": "you-need",
183
183
  "title": "You need",
184
184
  "kind": "requirements",
185
- "markdown": "**Owner** or **Admin** access in Play Console, plus a Google Cloud project where you can create a service account. The project does not need to be linked to Play Console."
185
+ "markdown": "**Owner** or **Admin** access in Play Console, and a Google Cloud project where you can create a service account. The project does not need to be linked to Play Console."
186
186
  },
187
187
  {
188
188
  "id": "click-path",
189
189
  "title": "Click path",
190
190
  "kind": "steps",
191
- "markdown": "### A. Google Cloud\n\n1. Open [Google Cloud](https://console.cloud.google.com) and select or create a project.\n2. Open **APIs & Services → Library**. Find **Google Play Android Developer API** and click **Enable**.\n3. For statistics reports, also enable **Google Play Developer Reporting API**.\n4. Open **IAM & Admin → Service Accounts → Create service account**. Name it `sprid-play`, click **Create and continue**, then **Done**. Leave Cloud roles empty.\n5. Open the account → **Keys → Add key → Create new key → JSON → Create**. Save the download as `play-sa.json`; keep it private.\n6. Copy the service account’s email address.\n\n### B. Play Console\n\n7. Open [Play Console](https://play.google.com/console) → **Users and permissions → Invite new users**. Enter the service account’s email and leave access expiry unset.\n8. Under **App permissions → Add app**, select your app and click **Apply**.\n9. Enable **View app information (read-only)** and **Reply to reviews**. Add **Manage store presence** if you want to update listing copy. Leave financial access off.\n10. Click **Invite user → Send invite**."
191
+ "markdown": "### A. Google Cloud\n\n1. Open [Google Cloud](https://console.cloud.google.com) and select or create a project.\n2. **APIs & Services → Library**: enable **Google Play Android Developer API**. For statistics reports, also enable **Google Play Developer Reporting API**.\n3. **IAM & Admin → Service Accounts → Create service account**. Name it `sprid-play`, click **Create and continue**, then **Done**. Leave Cloud roles empty.\n4. Open the account → **Keys → Add key → Create new key → JSON → Create**. Save it as `play-sa.json` and keep it private.\n5. Copy the service account’s email.\n\n### B. Play Console\n\n6. Open [Play Console](https://play.google.com/console) → **Users and permissions → Invite new users**. Enter the service account’s email; leave access expiry unset.\n7. **App permissions → Add app**: select your app, then **Apply**.\n8. Enable **View app information (read-only)** and **Reply to reviews**. Add **Manage store presence** to update listing copy. Leave financial access off.\n9. Click **Invite user → Send invite**."
192
192
  },
193
193
  {
194
194
  "id": "find-the-package-name",
195
195
  "title": "Find the package name",
196
196
  "kind": "identifiers",
197
- "markdown": "Copy the package name beneath your app’s name on its Play Console **Dashboard**, such as `com.example.app`. Run `sprid init` if your Sprid app profile is missing it.\n\nFor download reports, copy the **Cloud Storage URI** from **Download reports → Statistics** into `playExportBucket` in `.sprid/app.json`. These reports also need the account-level **View app information and download bulk reports (read-only)** permission."
197
+ "markdown": "Copy it from beneath your app’s name on its Play Console **Dashboard**, such as `com.example.app`. Run `sprid init` if your Sprid app profile lacks it.\n\nFor download reports, copy the **Cloud Storage URI** from **Download reports → Statistics** into `playExportBucket` in `.sprid/app.json`. These also need the account-level **View app information and download bulk reports (read-only)** permission."
198
198
  },
199
199
  {
200
200
  "id": "then-run",
201
201
  "title": "Then run",
202
202
  "kind": "command",
203
- "markdown": "```\nsprid connect play --key ~/Downloads/play-sa.json\n```\n\nUse the path to your downloaded file. Do not paste its contents into chat."
203
+ "markdown": "```\nsprid connect play --key ~/Downloads/play-sa.json\n```\n\nUse your own file path. Keep its contents out of chat."
204
204
  },
205
205
  {
206
206
  "id": "how-to-check-it-worked",
207
207
  "title": "How to check it worked",
208
208
  "kind": "verify",
209
- "markdown": "Run `sprid status`, then ask your agent: “Check that Sprid can read this app’s Play reviews and download reports. Tell me what is missing.”\n\nThe review email only arrives when new reviews are available. Only recent reviews with written text can be imported initially."
209
+ "markdown": "Run `sprid status`, then ask your agent: “Check that Sprid can read this app’s Play reviews and download reports. Tell me what is missing.” The review email only arrives when there are new reviews."
210
210
  },
211
211
  {
212
212
  "id": "if-it-fails",
213
213
  "title": "If it fails",
214
214
  "kind": "troubleshooting",
215
- "markdown": "- **Permission denied:** check the service account appears under **Users and permissions** with your app selected. New permissions may take time to apply; integrators report up to 24 hours, which Google’s docs do not confirm.\n- **Google Play’s API is switched off:** enable **Google Play Android Developer API** in the Cloud project used to create the service account. Use the project link in Sprid’s error message.\n- **Reviews are empty:** older reviews and star-only ratings may be visible in the store but unavailable to Sprid."
215
+ "markdown": "- **Permission denied:** check the service account appears under **Users and permissions** with your app selected. New permissions can take time; integrators report up to 24 hours, which Google’s docs do not confirm.\n- **Google Play’s API is switched off:** enable **Google Play Android Developer API** in the service account’s Cloud project. Sprid’s error message links to it.\n- **Reviews are empty:** Google’s API only returns recent reviews with written text. Older reviews and star-only ratings stay visible in the store but not to Sprid."
216
+ },
217
+ {
218
+ "id": "investigate-with-this-connection",
219
+ "title": "Investigate with this connection",
220
+ "kind": "detail",
221
+ "markdown": "Reads `reviews` and `report_rows` for the pinned package. Reviews are limited by Google’s history window; report rows need the saved export bucket.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source play --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
216
222
  },
217
223
  {
218
224
  "id": "sources",
219
225
  "title": "Sources",
220
226
  "kind": "sources",
221
227
  "markdown": "- [Getting started](https://developers.google.com/android-publisher/getting_started)\n- [Permission names in Users and permissions](https://support.google.com/googleplay/android-developer/answer/9844686)\n- [Reviews API only returns recent reviews](https://developers.google.com/android-publisher/reply-to-reviews)\n- [24-hour propagation](https://docs.apphud.com/docs/google-play-service-credentials)\n- [24-hour propagation](https://documentation.qonversion.io/docs/service-account-key-android)"
222
- },
223
- {
224
- "id": "investigate-with-this-connection",
225
- "title": "Investigate with this connection",
226
- "kind": "detail",
227
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `reviews`, `report_rows`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source play --json\n```\n\nPinned package. Live reviews have provider history limits; exported acquisition rows require the saved Play export bucket. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification."
228
228
  }
229
229
  ],
230
- "markdown": "# Google Play\n\n**What Sprid does with this:** Read Google Play reviews and download reports, and update listing copy when you approve it.\n\n## You need\n\n**Owner** or **Admin** access in Play Console, plus a Google Cloud project where you can create a service account. The project does not need to be linked to Play Console.\n\n## Click path\n\n### A. Google Cloud\n\n1. Open [Google Cloud](https://console.cloud.google.com) and select or create a project.\n2. Open **APIs & Services → Library**. Find **Google Play Android Developer API** and click **Enable**.\n3. For statistics reports, also enable **Google Play Developer Reporting API**.\n4. Open **IAM & Admin → Service Accounts → Create service account**. Name it `sprid-play`, click **Create and continue**, then **Done**. Leave Cloud roles empty.\n5. Open the account → **Keys → Add key → Create new key → JSON → Create**. Save the download as `play-sa.json`; keep it private.\n6. Copy the service account’s email address.\n\n### B. Play Console\n\n7. Open [Play Console](https://play.google.com/console) → **Users and permissions → Invite new users**. Enter the service account’s email and leave access expiry unset.\n8. Under **App permissions → Add app**, select your app and click **Apply**.\n9. Enable **View app information (read-only)** and **Reply to reviews**. Add **Manage store presence** if you want to update listing copy. Leave financial access off.\n10. Click **Invite user → Send invite**.\n\n## Find the package name\n\nCopy the package name beneath your app’s name on its Play Console **Dashboard**, such as `com.example.app`. Run `sprid init` if your Sprid app profile is missing it.\n\nFor download reports, copy the **Cloud Storage URI** from **Download reports → Statistics** into `playExportBucket` in `.sprid/app.json`. These reports also need the account-level **View app information and download bulk reports (read-only)** permission.\n\n## Then run\n\n```\nsprid connect play --key ~/Downloads/play-sa.json\n```\n\nUse the path to your downloaded file. Do not paste its contents into chat.\n\n## How to check it worked\n\nRun `sprid status`, then ask your agent: “Check that Sprid can read this app’s Play reviews and download reports. Tell me what is missing.”\n\nThe review email only arrives when new reviews are available. Only recent reviews with written text can be imported initially.\n\n## If it fails\n\n- **Permission denied:** check the service account appears under **Users and permissions** with your app selected. New permissions may take time to apply; integrators report up to 24 hours, which Google’s docs do not confirm.\n- **Google Play’s API is switched off:** enable **Google Play Android Developer API** in the Cloud project used to create the service account. Use the project link in Sprid’s error message.\n- **Reviews are empty:** older reviews and star-only ratings may be visible in the store but unavailable to Sprid.\n\n## Sources\n\n- [Getting started](https://developers.google.com/android-publisher/getting_started)\n- [Permission names in Users and permissions](https://support.google.com/googleplay/android-developer/answer/9844686)\n- [Reviews API only returns recent reviews](https://developers.google.com/android-publisher/reply-to-reviews)\n- [24-hour propagation](https://docs.apphud.com/docs/google-play-service-credentials)\n- [24-hour propagation](https://documentation.qonversion.io/docs/service-account-key-android)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `reviews`, `report_rows`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source play --json\n```\n\nPinned package. Live reviews have provider history limits; exported acquisition rows require the saved Play export bucket. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n",
231
- "revision": "7794554f047a38dd"
230
+ "markdown": "# Google Play\n\n**What Sprid does with this:** Read Google Play reviews and download reports, and update listing copy when you approve it.\n\n## You need\n\n**Owner** or **Admin** access in Play Console, and a Google Cloud project where you can create a service account. The project does not need to be linked to Play Console.\n\n## Click path\n\n### A. Google Cloud\n\n1. Open [Google Cloud](https://console.cloud.google.com) and select or create a project.\n2. **APIs & Services → Library**: enable **Google Play Android Developer API**. For statistics reports, also enable **Google Play Developer Reporting API**.\n3. **IAM & Admin → Service Accounts → Create service account**. Name it `sprid-play`, click **Create and continue**, then **Done**. Leave Cloud roles empty.\n4. Open the account → **Keys → Add key → Create new key → JSON → Create**. Save it as `play-sa.json` and keep it private.\n5. Copy the service account’s email.\n\n### B. Play Console\n\n6. Open [Play Console](https://play.google.com/console) → **Users and permissions → Invite new users**. Enter the service account’s email; leave access expiry unset.\n7. **App permissions → Add app**: select your app, then **Apply**.\n8. Enable **View app information (read-only)** and **Reply to reviews**. Add **Manage store presence** to update listing copy. Leave financial access off.\n9. Click **Invite user → Send invite**.\n\n## Find the package name\n\nCopy it from beneath your app’s name on its Play Console **Dashboard**, such as `com.example.app`. Run `sprid init` if your Sprid app profile lacks it.\n\nFor download reports, copy the **Cloud Storage URI** from **Download reports → Statistics** into `playExportBucket` in `.sprid/app.json`. These also need the account-level **View app information and download bulk reports (read-only)** permission.\n\n## Then run\n\n```\nsprid connect play --key ~/Downloads/play-sa.json\n```\n\nUse your own file path. Keep its contents out of chat.\n\n## How to check it worked\n\nRun `sprid status`, then ask your agent: “Check that Sprid can read this app’s Play reviews and download reports. Tell me what is missing.” The review email only arrives when there are new reviews.\n\n## If it fails\n\n- **Permission denied:** check the service account appears under **Users and permissions** with your app selected. New permissions can take time; integrators report up to 24 hours, which Google’s docs do not confirm.\n- **Google Play’s API is switched off:** enable **Google Play Android Developer API** in the service account’s Cloud project. Sprid’s error message links to it.\n- **Reviews are empty:** Google’s API only returns recent reviews with written text. Older reviews and star-only ratings stay visible in the store but not to Sprid.\n\n## Investigate with this connection\n\nReads `reviews` and `report_rows` for the pinned package. Reviews are limited by Google’s history window; report rows need the saved export bucket.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source play --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources\n\n- [Getting started](https://developers.google.com/android-publisher/getting_started)\n- [Permission names in Users and permissions](https://support.google.com/googleplay/android-developer/answer/9844686)\n- [Reviews API only returns recent reviews](https://developers.google.com/android-publisher/reply-to-reviews)\n- [24-hour propagation](https://docs.apphud.com/docs/google-play-service-credentials)\n- [24-hour propagation](https://documentation.qonversion.io/docs/service-account-key-android)\n",
231
+ "revision": "af1abe2a96ae39ec"
232
232
  },
233
233
  {
234
234
  "id": "search-console",
@@ -241,53 +241,53 @@ export const GUIDES = [
241
241
  "id": "you-need",
242
242
  "title": "You need",
243
243
  "kind": "requirements",
244
- "markdown": "**Owner** access to your website’s Search Console property. You can reuse the Google service account created for Sprid’s Play connection."
244
+ "markdown": "**Owner** access to your website’s Search Console property. You can reuse the service account from Sprid’s Play connection."
245
245
  },
246
246
  {
247
247
  "id": "click-path",
248
248
  "title": "Click path",
249
249
  "kind": "steps",
250
- "markdown": "### A. Google Cloud\n\n1. Open [Google Cloud](https://console.cloud.google.com) and select the project for your service account.\n2. Under **APIs & Services → Library**, find **Google Search Console API** and click **Enable**.\n3. If you need a service account, open **IAM & Admin → Service Accounts → Create service account**, name it `sprid-gsc`, then click **Done**.\n4. Open the account → **Keys → Add key → Create new key → JSON**. Save the file as `gsc-sa.json` and copy the account’s email. If reusing an account, use its existing key file and email.\n\n### B. Search Console\n\n5. Open [Search Console](https://search.google.com/search-console) and select your website in the top-left dropdown.\n6. Open **Settings → Users and permissions → Add user**.\n7. Enter the service account’s email, choose **Restricted**, then click **Add**."
250
+ "markdown": "### A. Google Cloud\n\n1. Open [Google Cloud](https://console.cloud.google.com) and select your service account’s project.\n2. **APIs & Services → Library**: enable **Google Search Console API**.\n3. No service account yet? **IAM & Admin → Service Accounts → Create service account**, name it `sprid-gsc`, then **Done**.\n4. Open the account → **Keys → Add key → Create new key → JSON**. Save it as `gsc-sa.json` and copy the account’s email. Reusing an account? Use its existing key file and email.\n\n### B. Search Console\n\n5. Open [Search Console](https://search.google.com/search-console) and select your website in the top-left dropdown.\n6. **Settings → Users and permissions → Add user**.\n7. Enter the service account’s email, choose **Restricted**, then **Add**."
251
251
  },
252
252
  {
253
253
  "id": "domain-property-vs-url-prefix-and-the-property-string",
254
254
  "title": "Domain property vs URL prefix, and the property string",
255
255
  "kind": "identifiers",
256
- "markdown": "Use the property you just granted access to:\n\n| Property shown in Search Console | Value to use below |\n|---|---|\n| Domain, such as `example.com` | `sc-domain:example.com` |\n| URL prefix, such as `https://www.example.com/` | The exact URL, including its final slash |\n\nA domain property covers all versions of your domain. Check **Settings → Property settings** if unsure which kind you have."
256
+ "markdown": "| Property shown in Search Console | Value to use |\n|---|---|\n| Domain, such as `example.com` | `sc-domain:example.com` |\n| URL prefix, such as `https://www.example.com/` | The exact URL, including its final slash |\n\nA domain property covers every version of your domain. **Settings → Property settings** shows which kind you have."
257
257
  },
258
258
  {
259
259
  "id": "then-run",
260
260
  "title": "Then run",
261
261
  "kind": "command",
262
- "markdown": "```\nsprid connect gsc --key ~/Downloads/gsc-sa.json --property sc-domain:example.com\n```\n\nReplace the file path and property with yours. Keep the downloaded file private."
262
+ "markdown": "```\nsprid connect gsc --key ~/Downloads/gsc-sa.json --property sc-domain:example.com\n```\n\nUse your own file path and property. Keep the file private."
263
263
  },
264
264
  {
265
265
  "id": "how-to-check-it-worked",
266
266
  "title": "How to check it worked",
267
267
  "kind": "verify",
268
- "markdown": "Run `sprid status`, then ask your agent: “Read recent Google search results for this website through Sprid and check that the property is correct.”\n\nA saved key confirms setup; the live read confirms access. The newest dates may be missing because Google’s reports arrive late."
268
+ "markdown": "Run `sprid status`, then ask your agent: “Read recent Google search results for this website through Sprid and check that the property is correct.” A saved key confirms setup; only the live read confirms access. The newest few days may be missing because Google reports late."
269
269
  },
270
270
  {
271
271
  "id": "if-it-fails",
272
272
  "title": "If it fails",
273
273
  "kind": "troubleshooting",
274
- "markdown": "- **Access denied:** check the service account’s email was added to the exact property in your command. If both match, try **Full** permission. Do not grant Owner.\n- **Property not found:** check the domain or URL, including `www`, `https` and the final slash.\n- **Recent dates are empty:** try an earlier period; the latest few days may still be processing."
274
+ "markdown": "- **Access denied:** check the service account’s email was added to the exact property in your command. If both match, try **Full** permission. Do not grant Owner.\n- **Property not found:** check the domain or URL, including `www`, `https` and the final slash.\n- **Recent dates are empty:** try an earlier period; the latest days may still be processing."
275
+ },
276
+ {
277
+ "id": "investigate-with-this-connection",
278
+ "title": "Investigate with this connection",
279
+ "kind": "detail",
280
+ "markdown": "Reads `search` for the pinned property. Dates are Pacific time; anonymized queries and top-row limits reduce coverage.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source gsc --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
275
281
  },
276
282
  {
277
283
  "id": "sources",
278
284
  "title": "Sources",
279
285
  "kind": "sources",
280
286
  "markdown": "- [Permission levels and Add user path](https://support.google.com/webmasters/answer/7687615)\n- [`siteUrl` formats](https://developers.google.com/webmaster-tools/v1/searchanalytics/query)\n- [Service account rights vs Owner](https://www.indexernow.com/fix/service-account-owner-gsc)"
281
- },
282
- {
283
- "id": "investigate-with-this-connection",
284
- "title": "Investigate with this connection",
285
- "kind": "detail",
286
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `search`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source gsc --json\n```\n\nPinned Search Console property. Dates use Pacific time; anonymized queries and top-row limits affect coverage. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification."
287
287
  }
288
288
  ],
289
- "markdown": "# Google Search Console\n\n**What Sprid does with this:** See which Google searches bring people to your website.\n\n## You need\n\n**Owner** access to your website’s Search Console property. You can reuse the Google service account created for Sprid’s Play connection.\n\n## Click path\n\n### A. Google Cloud\n\n1. Open [Google Cloud](https://console.cloud.google.com) and select the project for your service account.\n2. Under **APIs & Services → Library**, find **Google Search Console API** and click **Enable**.\n3. If you need a service account, open **IAM & Admin → Service Accounts → Create service account**, name it `sprid-gsc`, then click **Done**.\n4. Open the account → **Keys → Add key → Create new key → JSON**. Save the file as `gsc-sa.json` and copy the account’s email. If reusing an account, use its existing key file and email.\n\n### B. Search Console\n\n5. Open [Search Console](https://search.google.com/search-console) and select your website in the top-left dropdown.\n6. Open **Settings → Users and permissions → Add user**.\n7. Enter the service account’s email, choose **Restricted**, then click **Add**.\n\n## Domain property vs URL prefix, and the property string\n\nUse the property you just granted access to:\n\n| Property shown in Search Console | Value to use below |\n|---|---|\n| Domain, such as `example.com` | `sc-domain:example.com` |\n| URL prefix, such as `https://www.example.com/` | The exact URL, including its final slash |\n\nA domain property covers all versions of your domain. Check **Settings → Property settings** if unsure which kind you have.\n\n## Then run\n\n```\nsprid connect gsc --key ~/Downloads/gsc-sa.json --property sc-domain:example.com\n```\n\nReplace the file path and property with yours. Keep the downloaded file private.\n\n## How to check it worked\n\nRun `sprid status`, then ask your agent: “Read recent Google search results for this website through Sprid and check that the property is correct.”\n\nA saved key confirms setup; the live read confirms access. The newest dates may be missing because Google’s reports arrive late.\n\n## If it fails\n\n- **Access denied:** check the service account’s email was added to the exact property in your command. If both match, try **Full** permission. Do not grant Owner.\n- **Property not found:** check the domain or URL, including `www`, `https` and the final slash.\n- **Recent dates are empty:** try an earlier period; the latest few days may still be processing.\n\n## Sources\n\n- [Permission levels and Add user path](https://support.google.com/webmasters/answer/7687615)\n- [`siteUrl` formats](https://developers.google.com/webmaster-tools/v1/searchanalytics/query)\n- [Service account rights vs Owner](https://www.indexernow.com/fix/service-account-owner-gsc)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `search`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source gsc --json\n```\n\nPinned Search Console property. Dates use Pacific time; anonymized queries and top-row limits affect coverage. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n",
290
- "revision": "25575eceae015c1f"
289
+ "markdown": "# Google Search Console\n\n**What Sprid does with this:** See which Google searches bring people to your website.\n\n## You need\n\n**Owner** access to your website’s Search Console property. You can reuse the service account from Sprid’s Play connection.\n\n## Click path\n\n### A. Google Cloud\n\n1. Open [Google Cloud](https://console.cloud.google.com) and select your service account’s project.\n2. **APIs & Services → Library**: enable **Google Search Console API**.\n3. No service account yet? **IAM & Admin → Service Accounts → Create service account**, name it `sprid-gsc`, then **Done**.\n4. Open the account → **Keys → Add key → Create new key → JSON**. Save it as `gsc-sa.json` and copy the account’s email. Reusing an account? Use its existing key file and email.\n\n### B. Search Console\n\n5. Open [Search Console](https://search.google.com/search-console) and select your website in the top-left dropdown.\n6. **Settings → Users and permissions → Add user**.\n7. Enter the service account’s email, choose **Restricted**, then **Add**.\n\n## Domain property vs URL prefix, and the property string\n\n| Property shown in Search Console | Value to use |\n|---|---|\n| Domain, such as `example.com` | `sc-domain:example.com` |\n| URL prefix, such as `https://www.example.com/` | The exact URL, including its final slash |\n\nA domain property covers every version of your domain. **Settings → Property settings** shows which kind you have.\n\n## Then run\n\n```\nsprid connect gsc --key ~/Downloads/gsc-sa.json --property sc-domain:example.com\n```\n\nUse your own file path and property. Keep the file private.\n\n## How to check it worked\n\nRun `sprid status`, then ask your agent: “Read recent Google search results for this website through Sprid and check that the property is correct.” A saved key confirms setup; only the live read confirms access. The newest few days may be missing because Google reports late.\n\n## If it fails\n\n- **Access denied:** check the service account’s email was added to the exact property in your command. If both match, try **Full** permission. Do not grant Owner.\n- **Property not found:** check the domain or URL, including `www`, `https` and the final slash.\n- **Recent dates are empty:** try an earlier period; the latest days may still be processing.\n\n## Investigate with this connection\n\nReads `search` for the pinned property. Dates are Pacific time; anonymized queries and top-row limits reduce coverage.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source gsc --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources\n\n- [Permission levels and Add user path](https://support.google.com/webmasters/answer/7687615)\n- [`siteUrl` formats](https://developers.google.com/webmaster-tools/v1/searchanalytics/query)\n- [Service account rights vs Owner](https://www.indexernow.com/fix/service-account-owner-gsc)\n",
290
+ "revision": "517fc192cac5962e"
291
291
  },
292
292
  {
293
293
  "id": "posthog",
@@ -300,71 +300,71 @@ export const GUIDES = [
300
300
  "id": "you-need",
301
301
  "title": "You need",
302
302
  "kind": "requirements",
303
- "markdown": "Access to your app’s PostHog project and permission to create a **personal API key**. Your app must already send events to PostHog; connecting Sprid does not add tracking."
303
+ "markdown": "Access to your app’s PostHog project and permission to create a **personal API key**. Your app must already send events to PostHog; connecting Sprid adds no tracking. The public key inside your app does not work here."
304
304
  },
305
305
  {
306
306
  "id": "click-path-usposthogcom-or-euposthogcom",
307
307
  "title": "Click path (us.posthog.com or eu.posthog.com)",
308
308
  "kind": "steps",
309
- "markdown": "1. In PostHog, open **Settings → Account → Personal API keys**.\n2. Click **Create personal API key** and name it `Sprid <app name>`.\n3. Under access, select **Projects** and choose your app’s project. Leave **All access** off.\n4. Select **Query → Read** and **Project → Read**. Leave write access off.\n5. Create the key, copy it before closing the dialog and save it to a private file such as `~/keys/posthog-myapp.txt`.\n\nCreate a separate key for each app. The public key used inside your app cannot be used for this connection."
309
+ "markdown": "1. **Settings → Account → Personal API keys → Create personal API key**, named `Sprid <app name>`.\n2. Under access, select **Projects** and choose your app’s project. Leave **All access** off.\n3. Select **Query → Read** and **Project → Read**. Leave write access off.\n4. Copy the key before closing the dialog and save it to a private file such as `~/keys/posthog-myapp.txt`. Use a separate key per app."
310
310
  },
311
311
  {
312
312
  "id": "project-id-and-host",
313
313
  "title": "Project id and host",
314
314
  "kind": "identifiers",
315
- "markdown": "- **Project id:** the number after `/project/` in your PostHog address, such as `12345`.\n- **Host:** `eu` for `eu.posthog.com`, `us` for `us.posthog.com`, or your full self-hosted address. Use the address where you open the dashboard."
315
+ "markdown": "- **Project id:** the number after `/project/` in your PostHog address, such as `12345`.\n- **Host:** `eu` for `eu.posthog.com`, `us` for `us.posthog.com`, or your full self-hosted address, whichever you open the dashboard on."
316
316
  },
317
317
  {
318
318
  "id": "then-run",
319
319
  "title": "Then run",
320
320
  "kind": "command",
321
- "markdown": "```sh\nsprid connect posthog --app myapp --key ~/keys/posthog-myapp.txt --project 12345 --host eu\n```\n\nReplace `myapp` with your Sprid app slug and use your own file path, project id and host. Add `--workspace <slug>` if needed. Do not paste the key into chat."
321
+ "markdown": "```sh\nsprid connect posthog --app myapp --key ~/keys/posthog-myapp.txt --project 12345 --host eu\n```\n\nUse your own app slug, file path, project id and host. Add `--workspace <slug>` if needed. Keep the key out of chat."
322
322
  },
323
323
  {
324
324
  "id": "how-to-check-it-worked",
325
325
  "title": "How to check it worked",
326
326
  "kind": "verify",
327
- "markdown": "Ask your agent: “Check that Sprid can read recent PostHog events for this app, and confirm the project is correct.”\n\nA saved connection alone does not prove events are arriving. The check should report recent activity or explain why it is missing."
327
+ "markdown": "Ask your agent: “Check that Sprid can read recent PostHog events for this app, and confirm the project is correct.” The check reports recent activity or explains why there is none; a saved connection alone does not prove events arrive."
328
328
  },
329
329
  {
330
330
  "id": "metric-definitions",
331
331
  "title": "Metric definitions",
332
332
  "kind": "detail",
333
- "markdown": "Open **Settings → Apps → your app → PostHog**. Set the account-created event\nand the first UTC date from which tracking is complete. Defaults:\n\n| Metric | What counts |\n|---|---|\n| Registrations | First `sprid_signup` event per PostHog person, across all surfaces |\n| New app users | First native app activity, including anonymous people |\n| Active app users | Distinct people with native activity during the period |\n| Web visitors | Distinct website pageview visitors on the saved hostname |\n\nSend `sprid_signup` from your server **after account creation**, with the account\nid as `distinct_id`. Identify that same id in your clients. Never fire it on\nlogin, page load or install. Existing `posthogEvents.signup` mappings work too;\n`posthogConfig.registration.event` takes precedence. No matching event history\nmeans unavailable; recorded history with no new registrations means zero.\nPeriods before the tracking start are unavailable. Comparisons crossing it are\nwithheld, as are comparisons without a confirmed start. Backfill using original creation timestamps.\n\nNative defaults require `posthog-react-native` plus iOS/iPadOS/Android and reject\nexplicit web surfaces. Set `app_surface: 'web'` on Expo web and `'native'` on devices.\nWeb counts require a browser and exclude reported bots and crawler user agents.\nEvery metric excludes event/person `is_internal`, `is_test` and `sprid_test = true`.\nThese are observed identities, not a guarantee that every remaining visitor is human.\n\n### Custom properties\n\nExpand **Custom properties** for advanced rules. Example JSON:\n\n```json\n{\"registration\":{\"identity\":{\"scope\":\"event\",\"property\":\"account_id\"}},\"app\":{\"filters\":[{\"scope\":\"event\",\"property\":\"client_type\",\"operator\":\"in\",\"values\":[\"native\"]}]},\"exclude\":[{\"scope\":\"person\",\"property\":\"staff\",\"operator\":\"in\",\"values\":[true]}]}\n```\n\n`app.filters` replaces SDK/OS matching with your positive native rule. All app\nfilters must match. Each `exclude` rule removes matching traffic from all metrics.\n`registration.filters` narrows the selected event (for example `result = success`).\nUse `scope: event|person`, `operator: in|not_in` and string, number or boolean\n`values`. Missing properties fail `in` and pass `not_in`. Property names are literal\nkeys, including dots. Registration identity defaults to `person_id`; a configured\nidentity property must be present. It counts accounts, so it should be immutable.\n\nThe same object is `posthogConfig` in REST `PATCH /api/app-profiles/:id`, MCP\n`upsert_app_profile`, and the App Profile JSON used by `sprid init`. Add\n`registration.event` and `registration.since` there; the form edits those separately.\nNo credentials or SQL belong in this object. Fetch Insights after saving and check\nits registration notes and totals against your database before trusting a funnel."
333
+ "markdown": "In **Settings → Apps → your app → PostHog**, set the account-created event and the first UTC date from which tracking is complete.\n\n| Metric | What counts |\n|---|---|\n| Registrations | First `sprid_signup` event per PostHog person, across all surfaces |\n| New app users | First native app activity, including anonymous people |\n| Active app users | Distinct people with native activity in the period |\n| Web visitors | Distinct pageview visitors on the saved website hostname |\n\n**Registrations.** Send `sprid_signup` from your server after the account is created, with the account id as `distinct_id`, and identify that id in your clients. Never fire it on login, page load or install. An existing `posthogEvents.signup` mapping also works; `posthogConfig.registration.event` wins over it. No matching event history reads as unavailable; history with no new registrations reads as zero. Periods before the tracking start are unavailable, and comparisons crossing it, or with no confirmed start, are withheld. Backfill with the original creation timestamps.\n\n**App and web.** Native defaults need `posthog-react-native` on iOS, iPadOS or Android and reject explicit web surfaces: set `app_surface: 'web'` on Expo web and `'native'` on devices. Web counts need a browser and exclude reported bots and crawler user agents. Every metric excludes events or people with `is_internal`, `is_test` or `sprid_test = true`. What remains is observed identities, not guaranteed humans.\n\n### Custom properties\n\nAdvanced rules go under **Custom properties**:\n\n```json\n{\"registration\":{\"identity\":{\"scope\":\"event\",\"property\":\"account_id\"}},\"app\":{\"filters\":[{\"scope\":\"event\",\"property\":\"client_type\",\"operator\":\"in\",\"values\":[\"native\"]}]},\"exclude\":[{\"scope\":\"person\",\"property\":\"staff\",\"operator\":\"in\",\"values\":[true]}]}\n```\n\n- `app.filters` replaces SDK/OS matching with your own native rule; all filters must match.\n- `exclude` removes matching traffic from every metric.\n- `registration.filters` narrows the registration event, for example `result = success`.\n- `registration.identity` defaults to `person_id`. A configured property must be present and should never change, because it counts accounts.\n- Each rule takes `scope: event|person`, `operator: in|not_in` and string, number or boolean `values`. A missing property fails `in` and passes `not_in`. Property names are literal keys, dots included.\n\nThe same object is `posthogConfig` in REST `PATCH /api/app-profiles/:id`, MCP `upsert_app_profile` and the App Profile JSON used by `sprid init`, where `registration.event` and `registration.since` also live. No credentials or SQL belong in it. After saving, fetch Insights and check its registration notes and totals against your database before trusting a funnel."
334
334
  },
335
335
  {
336
336
  "id": "review-suspected-automated-traffic",
337
337
  "title": "Review suspected automated traffic",
338
338
  "kind": "detail",
339
- "markdown": "Open **Web visitors → Review traffic exclusions**, or **Settings → Apps → your\napp → PostHog → Review traffic**. Inspect a UTC date range, choose a traffic\ngroup and preview its effect before saving. A spike or a shared device\nfingerprint alone does not prove that visitors are bots. **How to tell a\nscraper from an audience, and the traps in doing it, are in\n[Check whether a traffic spike is real](https://sprid.studio/docs/traffic)**\n(`sprid docs traffic`). Read that before saving a rule.\n\nSaved exclusions apply by default to Sprid’s website totals, charts, breakdowns,\nsocial attribution and weekly website counts, including the comparison period.\nThey are app-specific and date-bounded. All properties inside a rule must match;\nmatching any enabled rule excludes the event. Remaining visitors are counted\nagain as distinct people, never by subtracting overlapping group totals.\nNative app activity and registrations are unchanged. **Restore this traffic**\ndisables a rule. Original PostHog data is never changed or deleted. Raw connected\nqueries and the marketing-review event inventory remain unfiltered evidence;\nuse Insights for the corrected website figures.\n\nAgents use MCP `review_traffic`, or REST\n`POST /api/app-profiles/:ref/traffic-review?workspaceId=…` with\n`{\"start\":\"2026-09-18\",\"end\":\"2026-09-19\"}`. End dates are exclusive, UTC;\nreview ranges are at most 31 days. An optional `exclusion` previews a rule\nagainst this period and the one before it. Save approved rules through\n`upsert_app_profile` or the profile PATCH route in\n`posthogConfig.trafficExclusions`, preserving the other configuration. Sprid\nrecords the last editor and update time. The vocabulary is provider-neutral;\ncurrently only PostHog is supported."
339
+ "markdown": "Read [Check whether a traffic spike is real](https://sprid.studio/docs/traffic) (`sprid docs traffic`) before saving a rule: a spike or a shared fingerprint alone does not prove bots.\n\nOpen **Web visitors → Review traffic exclusions**, or **Settings → Apps → your app → PostHog → Review traffic**. Pick a UTC date range and a traffic group, preview the effect, then save. Agents use MCP `review_traffic` or `POST /api/app-profiles/:ref/traffic-review?workspaceId=…` with `{\"start\":\"2026-09-18\",\"end\":\"2026-09-19\"}`: end dates exclusive, UTC, at most 31 days. An optional `exclusion` previews a rule against this period and the one before. Save approved rules in `posthogConfig.trafficExclusions` through `upsert_app_profile` or the profile PATCH route, keeping the rest of the configuration. Sprid records who edited it and when.\n\nHow exclusions apply:\n\n- App-specific and date-bounded. Properties inside a rule are AND; enabled rules are OR.\n- Applied to website totals, charts, breakdowns, social attribution and weekly website counts, comparison period included. Remaining visitors are recounted as distinct people, never subtracted.\n- Native app activity and registrations are unchanged, and PostHog’s data is never changed or deleted. **Restore this traffic** disables a rule.\n- Raw connected queries and the marketing-review event inventory stay unfiltered as evidence; Insights shows the corrected figures.\n- Only PostHog is supported today."
340
340
  },
341
341
  {
342
342
  "id": "social-traffic-and-clip-comparisons",
343
343
  "title": "Social traffic and clip comparisons",
344
344
  "kind": "detail",
345
- "markdown": "Save the app’s website URL on its App Profile as well as the PostHog connection.\nInsights compares completed-day website sessions with recorded social view gains.\nShared bio traffic stays at channel level; only a dedicated tagged link identifies\nan individual publish. Older clips with metric activity remain candidates.\n\nCopy stable bio links and dedicated clip links from Insights. Preserve\n`utm_source`, `utm_medium`, `sprid_account` and, for dedicated links only,\n`sprid_publish` through redirects. Never rotate the shared bio link to the newest\npublish. The optional bio-page HTML export records selections separately and\nkeeps a direct app link. Host it on the saved website hostname, with your existing\nPostHog initialization.\n\nTo capture store-link clicks and explicit website outcomes, include after the\nsite’s existing PostHog initialization:\n\n```html\n<script src=\"https://sprid.studio/sprid-attribution.js\" defer></script>\n```\n\nThis adapter uses your client and consent state; it sends nothing to Sprid.\nCall `window.spridAttribution?.track('signup')` or `.track('activation')` only\nafter that action succeeds. Existing App Profile `posthogEvents.signup` and\n`posthogEvents.activation` mappings are also accepted when the events share the\narriving website session. The adapter does not add tracking to a native app or\njoin website visits to purchases. A store click is not an install.\n\nMissing outcomes stay unmeasured. Redirect-only flows need a pre-navigation\nbeacon; redirect events are counted separately from website sessions.\n`get_insights` and `sprid insights` return the same evidence as the dashboard."
345
+ "markdown": "Save the app’s website URL on its App Profile as well as connecting PostHog. Insights then compares completed-day website sessions with recorded social view gains. Shared bio traffic stays at channel level; only a dedicated tagged link identifies one publish. Older clips with metric activity stay candidates.\n\nCopy the stable bio link and dedicated clip links from Insights. Keep `utm_source`, `utm_medium`, `sprid_account` and, on dedicated links only, `sprid_publish` through any redirects. Never repoint the shared bio link to the newest publish. The optional bio-page HTML export records selections separately and keeps a direct app link; host it on the saved hostname with your existing PostHog setup.\n\nTo capture store-link clicks and website outcomes, add after your PostHog initialization:\n\n```html\n<script src=\"https://sprid.studio/sprid-attribution.js\" defer></script>\n```\n\nIt uses your PostHog client and consent state and sends nothing to Sprid. Call `window.spridAttribution?.track('signup')` or `.track('activation')` only after that action succeeds. `posthogEvents.signup` and `posthogEvents.activation` mappings also count when the event shares the arriving website session. It does not track a native app or join visits to purchases, and a store click is not an install. Missing outcomes stay unmeasured. Redirect-only flows need a beacon before navigating; redirect events are counted apart from sessions. `get_insights` and `sprid insights` return the same evidence as the dashboard."
346
346
  },
347
347
  {
348
348
  "id": "if-it-fails",
349
349
  "title": "If it fails",
350
350
  "kind": "troubleshooting",
351
- "markdown": "- **Access denied:** check the key is active, grants your project and has both Read permissions above. Reconnect with the corrected key file.\n- **Project not found:** check the project number and host together. A US project needs the US host.\n- **Connected but no events:** check the dates and project first. Then ask your agent to check whether your app’s tracking is sending events."
352
- },
353
- {
354
- "id": "sources-and-verification",
355
- "title": "Sources and verification",
356
- "kind": "sources",
357
- "markdown": "Setup and live data reads checked on 2026-09-09.\n\n- [PostHog personal API keys](https://posthog.com/docs/api/personal-api-keys)\n- [Project identity endpoint](https://posthog.com/docs/api/projects)\n- [HogQL query endpoint](https://posthog.com/docs/api/query)\n- [SDK and framework guides](https://posthog.com/docs/libraries)\n- [React Native screen tracking](https://posthog.com/docs/libraries/react-native)\n- [Identifying users](https://posthog.com/docs/product-analytics/identify)\n- [Funnels](https://posthog.com/docs/product-analytics/funnels)"
351
+ "markdown": "- **Access denied:** check the key is active, grants your project and has both Read permissions. Reconnect with a corrected key file.\n- **Project not found:** check project number and host together. A US project needs the US host.\n- **Connected but no events:** check the dates and project, then ask your agent to check your app’s tracking is sending events."
358
352
  },
359
353
  {
360
354
  "id": "investigate-with-this-connection",
361
355
  "title": "Investigate with this connection",
362
356
  "kind": "detail",
363
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `query`, `events`, `properties`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source posthog --json\n```\n\nProject-pinned analytics. SQL is HogQL; inspect instrumentation before interpreting events. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n\nEvent-definition discovery additionally needs `event_definition:read`; property-definition discovery needs `property_definition:read`. Keep `query:read` and `project:read` for SQL and project verification. Restrict all of them to the saved project."
357
+ "markdown": "Reads `query` (HogQL), `events` and `properties` for the pinned project. Check instrumentation before interpreting events. Event-definition discovery also needs `event_definition:read`, property-definition discovery `property_definition:read`, both restricted to the same project.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source posthog --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
358
+ },
359
+ {
360
+ "id": "sources-and-verification",
361
+ "title": "Sources and verification",
362
+ "kind": "sources",
363
+ "markdown": "Setup and live reads checked 2026-09-09.\n\n- [PostHog personal API keys](https://posthog.com/docs/api/personal-api-keys)\n- [Project identity endpoint](https://posthog.com/docs/api/projects)\n- [HogQL query endpoint](https://posthog.com/docs/api/query)\n- [SDK and framework guides](https://posthog.com/docs/libraries)\n- [React Native screen tracking](https://posthog.com/docs/libraries/react-native)\n- [Identifying users](https://posthog.com/docs/product-analytics/identify)\n- [Funnels](https://posthog.com/docs/product-analytics/funnels)"
364
364
  }
365
365
  ],
366
- "markdown": "# PostHog\n\n**What Sprid does with this:** See how people use your app and where they stop.\n\n## You need\n\nAccess to your app’s PostHog project and permission to create a **personal API key**. Your app must already send events to PostHog; connecting Sprid does not add tracking.\n\n## Click path (us.posthog.com or eu.posthog.com)\n\n1. In PostHog, open **Settings → Account → Personal API keys**.\n2. Click **Create personal API key** and name it `Sprid <app name>`.\n3. Under access, select **Projects** and choose your app’s project. Leave **All access** off.\n4. Select **Query → Read** and **Project → Read**. Leave write access off.\n5. Create the key, copy it before closing the dialog and save it to a private file such as `~/keys/posthog-myapp.txt`.\n\nCreate a separate key for each app. The public key used inside your app cannot be used for this connection.\n\n## Project id and host\n\n- **Project id:** the number after `/project/` in your PostHog address, such as `12345`.\n- **Host:** `eu` for `eu.posthog.com`, `us` for `us.posthog.com`, or your full self-hosted address. Use the address where you open the dashboard.\n\n## Then run\n\n```sh\nsprid connect posthog --app myapp --key ~/keys/posthog-myapp.txt --project 12345 --host eu\n```\n\nReplace `myapp` with your Sprid app slug and use your own file path, project id and host. Add `--workspace <slug>` if needed. Do not paste the key into chat.\n\n## How to check it worked\n\nAsk your agent: “Check that Sprid can read recent PostHog events for this app, and confirm the project is correct.”\n\nA saved connection alone does not prove events are arriving. The check should report recent activity or explain why it is missing.\n\n## Metric definitions\n\nOpen **Settings → Apps → your app → PostHog**. Set the account-created event\nand the first UTC date from which tracking is complete. Defaults:\n\n| Metric | What counts |\n|---|---|\n| Registrations | First `sprid_signup` event per PostHog person, across all surfaces |\n| New app users | First native app activity, including anonymous people |\n| Active app users | Distinct people with native activity during the period |\n| Web visitors | Distinct website pageview visitors on the saved hostname |\n\nSend `sprid_signup` from your server **after account creation**, with the account\nid as `distinct_id`. Identify that same id in your clients. Never fire it on\nlogin, page load or install. Existing `posthogEvents.signup` mappings work too;\n`posthogConfig.registration.event` takes precedence. No matching event history\nmeans unavailable; recorded history with no new registrations means zero.\nPeriods before the tracking start are unavailable. Comparisons crossing it are\nwithheld, as are comparisons without a confirmed start. Backfill using original creation timestamps.\n\nNative defaults require `posthog-react-native` plus iOS/iPadOS/Android and reject\nexplicit web surfaces. Set `app_surface: 'web'` on Expo web and `'native'` on devices.\nWeb counts require a browser and exclude reported bots and crawler user agents.\nEvery metric excludes event/person `is_internal`, `is_test` and `sprid_test = true`.\nThese are observed identities, not a guarantee that every remaining visitor is human.\n\n### Custom properties\n\nExpand **Custom properties** for advanced rules. Example JSON:\n\n```json\n{\"registration\":{\"identity\":{\"scope\":\"event\",\"property\":\"account_id\"}},\"app\":{\"filters\":[{\"scope\":\"event\",\"property\":\"client_type\",\"operator\":\"in\",\"values\":[\"native\"]}]},\"exclude\":[{\"scope\":\"person\",\"property\":\"staff\",\"operator\":\"in\",\"values\":[true]}]}\n```\n\n`app.filters` replaces SDK/OS matching with your positive native rule. All app\nfilters must match. Each `exclude` rule removes matching traffic from all metrics.\n`registration.filters` narrows the selected event (for example `result = success`).\nUse `scope: event|person`, `operator: in|not_in` and string, number or boolean\n`values`. Missing properties fail `in` and pass `not_in`. Property names are literal\nkeys, including dots. Registration identity defaults to `person_id`; a configured\nidentity property must be present. It counts accounts, so it should be immutable.\n\nThe same object is `posthogConfig` in REST `PATCH /api/app-profiles/:id`, MCP\n`upsert_app_profile`, and the App Profile JSON used by `sprid init`. Add\n`registration.event` and `registration.since` there; the form edits those separately.\nNo credentials or SQL belong in this object. Fetch Insights after saving and check\nits registration notes and totals against your database before trusting a funnel.\n\n## Review suspected automated traffic\n\nOpen **Web visitors → Review traffic exclusions**, or **Settings → Apps → your\napp → PostHog → Review traffic**. Inspect a UTC date range, choose a traffic\ngroup and preview its effect before saving. A spike or a shared device\nfingerprint alone does not prove that visitors are bots. **How to tell a\nscraper from an audience, and the traps in doing it, are in\n[Check whether a traffic spike is real](https://sprid.studio/docs/traffic)**\n(`sprid docs traffic`). Read that before saving a rule.\n\nSaved exclusions apply by default to Sprid’s website totals, charts, breakdowns,\nsocial attribution and weekly website counts, including the comparison period.\nThey are app-specific and date-bounded. All properties inside a rule must match;\nmatching any enabled rule excludes the event. Remaining visitors are counted\nagain as distinct people, never by subtracting overlapping group totals.\nNative app activity and registrations are unchanged. **Restore this traffic**\ndisables a rule. Original PostHog data is never changed or deleted. Raw connected\nqueries and the marketing-review event inventory remain unfiltered evidence;\nuse Insights for the corrected website figures.\n\nAgents use MCP `review_traffic`, or REST\n`POST /api/app-profiles/:ref/traffic-review?workspaceId=…` with\n`{\"start\":\"2026-09-18\",\"end\":\"2026-09-19\"}`. End dates are exclusive, UTC;\nreview ranges are at most 31 days. An optional `exclusion` previews a rule\nagainst this period and the one before it. Save approved rules through\n`upsert_app_profile` or the profile PATCH route in\n`posthogConfig.trafficExclusions`, preserving the other configuration. Sprid\nrecords the last editor and update time. The vocabulary is provider-neutral;\ncurrently only PostHog is supported.\n\n## Social traffic and clip comparisons\n\nSave the app’s website URL on its App Profile as well as the PostHog connection.\nInsights compares completed-day website sessions with recorded social view gains.\nShared bio traffic stays at channel level; only a dedicated tagged link identifies\nan individual publish. Older clips with metric activity remain candidates.\n\nCopy stable bio links and dedicated clip links from Insights. Preserve\n`utm_source`, `utm_medium`, `sprid_account` and, for dedicated links only,\n`sprid_publish` through redirects. Never rotate the shared bio link to the newest\npublish. The optional bio-page HTML export records selections separately and\nkeeps a direct app link. Host it on the saved website hostname, with your existing\nPostHog initialization.\n\nTo capture store-link clicks and explicit website outcomes, include after the\nsite’s existing PostHog initialization:\n\n```html\n<script src=\"https://sprid.studio/sprid-attribution.js\" defer></script>\n```\n\nThis adapter uses your client and consent state; it sends nothing to Sprid.\nCall `window.spridAttribution?.track('signup')` or `.track('activation')` only\nafter that action succeeds. Existing App Profile `posthogEvents.signup` and\n`posthogEvents.activation` mappings are also accepted when the events share the\narriving website session. The adapter does not add tracking to a native app or\njoin website visits to purchases. A store click is not an install.\n\nMissing outcomes stay unmeasured. Redirect-only flows need a pre-navigation\nbeacon; redirect events are counted separately from website sessions.\n`get_insights` and `sprid insights` return the same evidence as the dashboard.\n\n## If it fails\n\n- **Access denied:** check the key is active, grants your project and has both Read permissions above. Reconnect with the corrected key file.\n- **Project not found:** check the project number and host together. A US project needs the US host.\n- **Connected but no events:** check the dates and project first. Then ask your agent to check whether your app’s tracking is sending events.\n\n## Sources and verification\n\nSetup and live data reads checked on 2026-09-09.\n\n- [PostHog personal API keys](https://posthog.com/docs/api/personal-api-keys)\n- [Project identity endpoint](https://posthog.com/docs/api/projects)\n- [HogQL query endpoint](https://posthog.com/docs/api/query)\n- [SDK and framework guides](https://posthog.com/docs/libraries)\n- [React Native screen tracking](https://posthog.com/docs/libraries/react-native)\n- [Identifying users](https://posthog.com/docs/product-analytics/identify)\n- [Funnels](https://posthog.com/docs/product-analytics/funnels)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `query`, `events`, `properties`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source posthog --json\n```\n\nProject-pinned analytics. SQL is HogQL; inspect instrumentation before interpreting events. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n\nEvent-definition discovery additionally needs `event_definition:read`; property-definition discovery needs `property_definition:read`. Keep `query:read` and `project:read` for SQL and project verification. Restrict all of them to the saved project.\n",
367
- "revision": "b1fa20dbfff1d9ca"
366
+ "markdown": "# PostHog\n\n**What Sprid does with this:** See how people use your app and where they stop.\n\n## You need\n\nAccess to your app’s PostHog project and permission to create a **personal API key**. Your app must already send events to PostHog; connecting Sprid adds no tracking. The public key inside your app does not work here.\n\n## Click path (us.posthog.com or eu.posthog.com)\n\n1. **Settings → Account → Personal API keys → Create personal API key**, named `Sprid <app name>`.\n2. Under access, select **Projects** and choose your app’s project. Leave **All access** off.\n3. Select **Query → Read** and **Project → Read**. Leave write access off.\n4. Copy the key before closing the dialog and save it to a private file such as `~/keys/posthog-myapp.txt`. Use a separate key per app.\n\n## Project id and host\n\n- **Project id:** the number after `/project/` in your PostHog address, such as `12345`.\n- **Host:** `eu` for `eu.posthog.com`, `us` for `us.posthog.com`, or your full self-hosted address, whichever you open the dashboard on.\n\n## Then run\n\n```sh\nsprid connect posthog --app myapp --key ~/keys/posthog-myapp.txt --project 12345 --host eu\n```\n\nUse your own app slug, file path, project id and host. Add `--workspace <slug>` if needed. Keep the key out of chat.\n\n## How to check it worked\n\nAsk your agent: “Check that Sprid can read recent PostHog events for this app, and confirm the project is correct.” The check reports recent activity or explains why there is none; a saved connection alone does not prove events arrive.\n\n## Metric definitions\n\nIn **Settings → Apps → your app → PostHog**, set the account-created event and the first UTC date from which tracking is complete.\n\n| Metric | What counts |\n|---|---|\n| Registrations | First `sprid_signup` event per PostHog person, across all surfaces |\n| New app users | First native app activity, including anonymous people |\n| Active app users | Distinct people with native activity in the period |\n| Web visitors | Distinct pageview visitors on the saved website hostname |\n\n**Registrations.** Send `sprid_signup` from your server after the account is created, with the account id as `distinct_id`, and identify that id in your clients. Never fire it on login, page load or install. An existing `posthogEvents.signup` mapping also works; `posthogConfig.registration.event` wins over it. No matching event history reads as unavailable; history with no new registrations reads as zero. Periods before the tracking start are unavailable, and comparisons crossing it, or with no confirmed start, are withheld. Backfill with the original creation timestamps.\n\n**App and web.** Native defaults need `posthog-react-native` on iOS, iPadOS or Android and reject explicit web surfaces: set `app_surface: 'web'` on Expo web and `'native'` on devices. Web counts need a browser and exclude reported bots and crawler user agents. Every metric excludes events or people with `is_internal`, `is_test` or `sprid_test = true`. What remains is observed identities, not guaranteed humans.\n\n### Custom properties\n\nAdvanced rules go under **Custom properties**:\n\n```json\n{\"registration\":{\"identity\":{\"scope\":\"event\",\"property\":\"account_id\"}},\"app\":{\"filters\":[{\"scope\":\"event\",\"property\":\"client_type\",\"operator\":\"in\",\"values\":[\"native\"]}]},\"exclude\":[{\"scope\":\"person\",\"property\":\"staff\",\"operator\":\"in\",\"values\":[true]}]}\n```\n\n- `app.filters` replaces SDK/OS matching with your own native rule; all filters must match.\n- `exclude` removes matching traffic from every metric.\n- `registration.filters` narrows the registration event, for example `result = success`.\n- `registration.identity` defaults to `person_id`. A configured property must be present and should never change, because it counts accounts.\n- Each rule takes `scope: event|person`, `operator: in|not_in` and string, number or boolean `values`. A missing property fails `in` and passes `not_in`. Property names are literal keys, dots included.\n\nThe same object is `posthogConfig` in REST `PATCH /api/app-profiles/:id`, MCP `upsert_app_profile` and the App Profile JSON used by `sprid init`, where `registration.event` and `registration.since` also live. No credentials or SQL belong in it. After saving, fetch Insights and check its registration notes and totals against your database before trusting a funnel.\n\n## Review suspected automated traffic\n\nRead [Check whether a traffic spike is real](https://sprid.studio/docs/traffic) (`sprid docs traffic`) before saving a rule: a spike or a shared fingerprint alone does not prove bots.\n\nOpen **Web visitors → Review traffic exclusions**, or **Settings → Apps → your app → PostHog → Review traffic**. Pick a UTC date range and a traffic group, preview the effect, then save. Agents use MCP `review_traffic` or `POST /api/app-profiles/:ref/traffic-review?workspaceId=…` with `{\"start\":\"2026-09-18\",\"end\":\"2026-09-19\"}`: end dates exclusive, UTC, at most 31 days. An optional `exclusion` previews a rule against this period and the one before. Save approved rules in `posthogConfig.trafficExclusions` through `upsert_app_profile` or the profile PATCH route, keeping the rest of the configuration. Sprid records who edited it and when.\n\nHow exclusions apply:\n\n- App-specific and date-bounded. Properties inside a rule are AND; enabled rules are OR.\n- Applied to website totals, charts, breakdowns, social attribution and weekly website counts, comparison period included. Remaining visitors are recounted as distinct people, never subtracted.\n- Native app activity and registrations are unchanged, and PostHog’s data is never changed or deleted. **Restore this traffic** disables a rule.\n- Raw connected queries and the marketing-review event inventory stay unfiltered as evidence; Insights shows the corrected figures.\n- Only PostHog is supported today.\n\n## Social traffic and clip comparisons\n\nSave the app’s website URL on its App Profile as well as connecting PostHog. Insights then compares completed-day website sessions with recorded social view gains. Shared bio traffic stays at channel level; only a dedicated tagged link identifies one publish. Older clips with metric activity stay candidates.\n\nCopy the stable bio link and dedicated clip links from Insights. Keep `utm_source`, `utm_medium`, `sprid_account` and, on dedicated links only, `sprid_publish` through any redirects. Never repoint the shared bio link to the newest publish. The optional bio-page HTML export records selections separately and keeps a direct app link; host it on the saved hostname with your existing PostHog setup.\n\nTo capture store-link clicks and website outcomes, add after your PostHog initialization:\n\n```html\n<script src=\"https://sprid.studio/sprid-attribution.js\" defer></script>\n```\n\nIt uses your PostHog client and consent state and sends nothing to Sprid. Call `window.spridAttribution?.track('signup')` or `.track('activation')` only after that action succeeds. `posthogEvents.signup` and `posthogEvents.activation` mappings also count when the event shares the arriving website session. It does not track a native app or join visits to purchases, and a store click is not an install. Missing outcomes stay unmeasured. Redirect-only flows need a beacon before navigating; redirect events are counted apart from sessions. `get_insights` and `sprid insights` return the same evidence as the dashboard.\n\n## If it fails\n\n- **Access denied:** check the key is active, grants your project and has both Read permissions. Reconnect with a corrected key file.\n- **Project not found:** check project number and host together. A US project needs the US host.\n- **Connected but no events:** check the dates and project, then ask your agent to check your app’s tracking is sending events.\n\n## Investigate with this connection\n\nReads `query` (HogQL), `events` and `properties` for the pinned project. Check instrumentation before interpreting events. Event-definition discovery also needs `event_definition:read`, property-definition discovery `property_definition:read`, both restricted to the same project.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source posthog --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources and verification\n\nSetup and live reads checked 2026-09-09.\n\n- [PostHog personal API keys](https://posthog.com/docs/api/personal-api-keys)\n- [Project identity endpoint](https://posthog.com/docs/api/projects)\n- [HogQL query endpoint](https://posthog.com/docs/api/query)\n- [SDK and framework guides](https://posthog.com/docs/libraries)\n- [React Native screen tracking](https://posthog.com/docs/libraries/react-native)\n- [Identifying users](https://posthog.com/docs/product-analytics/identify)\n- [Funnels](https://posthog.com/docs/product-analytics/funnels)\n",
367
+ "revision": "045b47b5e8256c17"
368
368
  },
369
369
  {
370
370
  "id": "revenuecat",
@@ -377,59 +377,59 @@ export const GUIDES = [
377
377
  "id": "you-need",
378
378
  "title": "You need",
379
379
  "kind": "requirements",
380
- "markdown": "**Admin** access to the RevenueCat project. Create a dedicated **V2 secret API key**; the app’s public key and V1 keys cannot be used here."
380
+ "markdown": "**Admin** access to the RevenueCat project, to create a dedicated **V2 secret API key**. The app’s public key and V1 keys do not work."
381
381
  },
382
382
  {
383
383
  "id": "click-path-apprevenuecatcom",
384
384
  "title": "Click path (app.revenuecat.com)",
385
385
  "kind": "steps",
386
- "markdown": "1. Select your project and open **API keys → Secret API keys → New secret API key**.\n2. Name it `Sprid analytics` and select **V2**.\n3. Under **Charts metrics permissions**, set **Overview Configuration Access Level** and **Charts Configuration Access Level** to **Read only**.\n4. Under **Project configuration permissions**, set **Apps Configuration Access Level** to **Read only**. Leave everything else at **No access**.\n5. Click **Generate** and save the key to a private file such as `~/keys/revenuecat-myapp.txt`.\n\nUse a separate key and filename for each app."
386
+ "markdown": "1. Select your project → **API keys → Secret API keys → New secret API key**.\n2. Name it `Sprid analytics` and select **V2**.\n3. **Charts metrics permissions**: set **Overview Configuration Access Level** and **Charts Configuration Access Level** to **Read only**.\n4. **Project configuration permissions**: set **Apps Configuration Access Level** to **Read only**. Leave everything else at **No access**.\n5. Click **Generate** and save the key to a private file such as `~/keys/revenuecat-myapp.txt`. Use a separate key per app."
387
387
  },
388
388
  {
389
389
  "id": "project-id",
390
390
  "title": "Project id",
391
391
  "kind": "identifiers",
392
- "markdown": "Open **Project settings → General** and copy the full **Project ID**, such as `proj1ab2c3d4`. Use this field, not the shorter id in the dashboard address."
392
+ "markdown": "**Project settings → General → Project ID**, such as `proj1ab2c3d4`. Not the shorter id in the dashboard address."
393
393
  },
394
394
  {
395
395
  "id": "then-run",
396
396
  "title": "Then run",
397
397
  "kind": "command",
398
- "markdown": "```sh\nsprid connect revenuecat --app myapp --key ~/keys/revenuecat-myapp.txt --project proj1ab2c3d4\n```\n\nReplace `myapp`, the file path and project id with yours. Add `--workspace <slug>` if needed. Keep the key out of chat."
398
+ "markdown": "```sh\nsprid connect revenuecat --app myapp --key ~/keys/revenuecat-myapp.txt --project proj1ab2c3d4\n```\n\nUse your own app slug, file path and project id. Add `--workspace <slug>` if needed. Keep the key out of chat."
399
399
  },
400
400
  {
401
401
  "id": "how-to-check-it-worked",
402
402
  "title": "How to check it worked",
403
403
  "kind": "verify",
404
- "markdown": "Ask your agent: “Check that Sprid can read this app’s RevenueCat overview and daily revenue history. Report any missing dates.”\n\nA saved key alone does not confirm access. An overview can work while history is unavailable; missing dates should not be treated as zero sales."
404
+ "markdown": "Ask your agent: “Check that Sprid can read this app’s RevenueCat overview and daily revenue history. Report any missing dates.” A saved key alone does not confirm access. The overview can work while history does not; missing dates are not zero sales."
405
405
  },
406
406
  {
407
407
  "id": "if-you-also-sell-on-the-web",
408
408
  "title": "If you also sell on the web",
409
409
  "kind": "detail",
410
- "markdown": "Connect services that record separate sales. Sprid withholds combined totals when RevenueCat may include the same Stripe or Paddle sales. Separate sales also need compatible currencies and measurement definitions before addition. RevenueCat Web Billing and your own Stripe sales use separate merchant accounts."
410
+ "markdown": "Connect the services that record the other sales. Sprid withholds a combined total when RevenueCat may already include the same Stripe or Paddle sales, or when currencies or definitions differ. RevenueCat Web Billing and your own Stripe are separate merchant accounts, so they never overlap."
411
411
  },
412
412
  {
413
413
  "id": "if-it-fails",
414
414
  "title": "If it fails",
415
415
  "kind": "troubleshooting",
416
- "markdown": "- **Key rejected:** check it is an active V2 secret key for the intended project.\n- **Access denied or history missing:** open the key’s **More → Edit**, check all three Read only settings, then **Submit**. You can edit the existing key.\n- **Project not found:** copy the full Project ID from **Project settings → General**. The key and id must belong to the same project.\n- **Too many requests:** retry later; creating another key will not help."
417
- },
418
- {
419
- "id": "sources-and-verification",
420
- "title": "Sources and verification",
421
- "kind": "sources",
422
- "markdown": "Setup and live data reads checked on 2026-09-09.\n\n- [RevenueCat API keys](https://www.revenuecat.com/docs/projects/authentication)\n- [API V2 reference](https://www.revenuecat.com/docs/api-v2)"
416
+ "markdown": "- **Key rejected:** check it is an active V2 secret key for this project.\n- **Access denied or history missing:** open the key’s **More → Edit**, check all three Read only settings, then **Submit**. No new key needed.\n- **Project not found:** copy the full Project ID from **Project settings → General**. Key and id must belong to the same project.\n- **Too many requests:** retry later. Another key will not help."
423
417
  },
424
418
  {
425
419
  "id": "investigate-with-this-connection",
426
420
  "title": "Investigate with this connection",
427
421
  "kind": "detail",
428
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `chart_options`, `chart`, `subscriptions`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source revenuecat --json\n```\n\nPinned project. Discover chart options before selecting dimensions, filters or resolution; preserve returned measure units. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n\nCustomer subscription reads additionally need `customer_information:subscriptions:read`. They default to production; request sandbox explicitly when investigating test purchases. Chart reads keep the chart permissions described above."
422
+ "markdown": "Reads `chart_options`, `chart` and `subscriptions` for the pinned project. Read `chart_options` before picking dimensions, filters or resolution, and keep the returned units. `subscriptions` also needs `customer_information:subscriptions:read` and defaults to production; ask for sandbox to see test purchases.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source revenuecat --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
423
+ },
424
+ {
425
+ "id": "sources-and-verification",
426
+ "title": "Sources and verification",
427
+ "kind": "sources",
428
+ "markdown": "Setup and live reads checked 2026-09-09.\n\n- [RevenueCat API keys](https://www.revenuecat.com/docs/projects/authentication)\n- [API V2 reference](https://www.revenuecat.com/docs/api-v2)"
429
429
  }
430
430
  ],
431
- "markdown": "# RevenueCat\n\n**What Sprid does with this:** See subscription revenue and how it changes over time.\n\n## You need\n\n**Admin** access to the RevenueCat project. Create a dedicated **V2 secret API key**; the app’s public key and V1 keys cannot be used here.\n\n## Click path (app.revenuecat.com)\n\n1. Select your project and open **API keys → Secret API keys → New secret API key**.\n2. Name it `Sprid analytics` and select **V2**.\n3. Under **Charts metrics permissions**, set **Overview Configuration Access Level** and **Charts Configuration Access Level** to **Read only**.\n4. Under **Project configuration permissions**, set **Apps Configuration Access Level** to **Read only**. Leave everything else at **No access**.\n5. Click **Generate** and save the key to a private file such as `~/keys/revenuecat-myapp.txt`.\n\nUse a separate key and filename for each app.\n\n## Project id\n\nOpen **Project settings → General** and copy the full **Project ID**, such as `proj1ab2c3d4`. Use this field, not the shorter id in the dashboard address.\n\n## Then run\n\n```sh\nsprid connect revenuecat --app myapp --key ~/keys/revenuecat-myapp.txt --project proj1ab2c3d4\n```\n\nReplace `myapp`, the file path and project id with yours. Add `--workspace <slug>` if needed. Keep the key out of chat.\n\n## How to check it worked\n\nAsk your agent: “Check that Sprid can read this app’s RevenueCat overview and daily revenue history. Report any missing dates.”\n\nA saved key alone does not confirm access. An overview can work while history is unavailable; missing dates should not be treated as zero sales.\n\n## If you also sell on the web\n\nConnect services that record separate sales. Sprid withholds combined totals when RevenueCat may include the same Stripe or Paddle sales. Separate sales also need compatible currencies and measurement definitions before addition. RevenueCat Web Billing and your own Stripe sales use separate merchant accounts.\n\n## If it fails\n\n- **Key rejected:** check it is an active V2 secret key for the intended project.\n- **Access denied or history missing:** open the key’s **More → Edit**, check all three Read only settings, then **Submit**. You can edit the existing key.\n- **Project not found:** copy the full Project ID from **Project settings → General**. The key and id must belong to the same project.\n- **Too many requests:** retry later; creating another key will not help.\n\n## Sources and verification\n\nSetup and live data reads checked on 2026-09-09.\n\n- [RevenueCat API keys](https://www.revenuecat.com/docs/projects/authentication)\n- [API V2 reference](https://www.revenuecat.com/docs/api-v2)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `chart_options`, `chart`, `subscriptions`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source revenuecat --json\n```\n\nPinned project. Discover chart options before selecting dimensions, filters or resolution; preserve returned measure units. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n\nCustomer subscription reads additionally need `customer_information:subscriptions:read`. They default to production; request sandbox explicitly when investigating test purchases. Chart reads keep the chart permissions described above.\n",
432
- "revision": "5a6c59a9f37c0486"
431
+ "markdown": "# RevenueCat\n\n**What Sprid does with this:** See subscription revenue and how it changes over time.\n\n## You need\n\n**Admin** access to the RevenueCat project, to create a dedicated **V2 secret API key**. The app’s public key and V1 keys do not work.\n\n## Click path (app.revenuecat.com)\n\n1. Select your project → **API keys → Secret API keys → New secret API key**.\n2. Name it `Sprid analytics` and select **V2**.\n3. **Charts metrics permissions**: set **Overview Configuration Access Level** and **Charts Configuration Access Level** to **Read only**.\n4. **Project configuration permissions**: set **Apps Configuration Access Level** to **Read only**. Leave everything else at **No access**.\n5. Click **Generate** and save the key to a private file such as `~/keys/revenuecat-myapp.txt`. Use a separate key per app.\n\n## Project id\n\n**Project settings → General → Project ID**, such as `proj1ab2c3d4`. Not the shorter id in the dashboard address.\n\n## Then run\n\n```sh\nsprid connect revenuecat --app myapp --key ~/keys/revenuecat-myapp.txt --project proj1ab2c3d4\n```\n\nUse your own app slug, file path and project id. Add `--workspace <slug>` if needed. Keep the key out of chat.\n\n## How to check it worked\n\nAsk your agent: “Check that Sprid can read this app’s RevenueCat overview and daily revenue history. Report any missing dates.” A saved key alone does not confirm access. The overview can work while history does not; missing dates are not zero sales.\n\n## If you also sell on the web\n\nConnect the services that record the other sales. Sprid withholds a combined total when RevenueCat may already include the same Stripe or Paddle sales, or when currencies or definitions differ. RevenueCat Web Billing and your own Stripe are separate merchant accounts, so they never overlap.\n\n## If it fails\n\n- **Key rejected:** check it is an active V2 secret key for this project.\n- **Access denied or history missing:** open the key’s **More → Edit**, check all three Read only settings, then **Submit**. No new key needed.\n- **Project not found:** copy the full Project ID from **Project settings → General**. Key and id must belong to the same project.\n- **Too many requests:** retry later. Another key will not help.\n\n## Investigate with this connection\n\nReads `chart_options`, `chart` and `subscriptions` for the pinned project. Read `chart_options` before picking dimensions, filters or resolution, and keep the returned units. `subscriptions` also needs `customer_information:subscriptions:read` and defaults to production; ask for sandbox to see test purchases.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source revenuecat --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources and verification\n\nSetup and live reads checked 2026-09-09.\n\n- [RevenueCat API keys](https://www.revenuecat.com/docs/projects/authentication)\n- [API V2 reference](https://www.revenuecat.com/docs/api-v2)\n",
432
+ "revision": "69f20ef0200c0d7a"
433
433
  },
434
434
  {
435
435
  "id": "stripe",
@@ -442,19 +442,19 @@ export const GUIDES = [
442
442
  "id": "you-need",
443
443
  "title": "You need",
444
444
  "kind": "requirements",
445
- "markdown": "Permission to create a **restricted API key** in Stripe. Use a live, read-only key beginning with `rk_live_`."
445
+ "markdown": "Permission to create a **restricted API key** in Stripe: live and read-only, beginning with `rk_live_`."
446
446
  },
447
447
  {
448
448
  "id": "click-path-dashboardstripecom",
449
449
  "title": "Click path (dashboard.stripe.com)",
450
450
  "kind": "steps",
451
- "markdown": "1. Open [Stripe](https://dashboard.stripe.com) → **Developers → API keys → Restricted keys → Create restricted key**.\n2. Name it `Sprid`.\n3. Set these permissions to **Read**:\n - **Core → Charges** and **Account**\n - **Billing → Subscriptions**, **Invoices** and **Prices**\n4. Leave every other permission at **None**.\n5. Create the key, reveal it and save it to a private file such as `~/keys/stripe-sprid.txt`."
451
+ "markdown": "1. Open [Stripe](https://dashboard.stripe.com) → **Developers → API keys → Restricted keys → Create restricted key**, and name it `Sprid`.\n2. Set these to **Read**:\n - **Core → Charges** and **Account**\n - **Billing → Subscriptions**, **Invoices** and **Prices**\n3. Leave every other permission at **None**.\n4. Create the key and save it to a private file such as `~/keys/stripe-sprid.txt`."
452
452
  },
453
453
  {
454
454
  "id": "then-run",
455
455
  "title": "Then run",
456
456
  "kind": "command",
457
- "markdown": "```\nsprid connect stripe --app <slug> --key ~/keys/stripe-sprid.txt\n```\n\nReplace `<slug>` with your Sprid app slug and use your own file path. Keep the key out of chat."
457
+ "markdown": "```\nsprid connect stripe --app <slug> --key ~/keys/stripe-sprid.txt\n```\n\nUse your Sprid app slug and your own file path. Keep the key out of chat."
458
458
  },
459
459
  {
460
460
  "id": "how-to-check-it-worked",
@@ -466,35 +466,35 @@ export const GUIDES = [
466
466
  "id": "what-the-numbers-mean",
467
467
  "title": "What the numbers mean",
468
468
  "kind": "detail",
469
- "markdown": "MRR estimates current active and past-due subscription prices, spreading annual subscriptions over 12 months. It excludes trials and does not apply discounts, proration or tax, so it differs from Stripe’s dashboard definition. Revenue covers 28 complete calendar days; refunds restate original charge dates. Currencies remain separate."
469
+ "markdown": "MRR is current active and past-due subscription prices, with annual plans spread over 12 months. It excludes trials and ignores discounts, proration and tax, so it differs from Stripe’s dashboard. Revenue covers 28 complete calendar days; refunds restate the original charge date. Currencies stay separate."
470
470
  },
471
471
  {
472
472
  "id": "if-you-also-use-revenuecat",
473
473
  "title": "If you also use RevenueCat",
474
474
  "kind": "detail",
475
- "markdown": "If RevenueCat may already count these Stripe sales, Sprid withholds the combined total. Addition requires resolved overlap, matching currencies and compatible measurement definitions."
475
+ "markdown": "If RevenueCat may already count these Stripe sales, Sprid withholds the combined total until the overlap is resolved and currencies and definitions match."
476
476
  },
477
477
  {
478
478
  "id": "if-it-fails",
479
479
  "title": "If it fails",
480
480
  "kind": "troubleshooting",
481
- "markdown": "- **Key rejected:** check the copied value is complete and starts with `rk_live_`.\n- **Permission denied:** check all Read permissions listed above and reconnect with a corrected key.\n- **MRR is zero but sales appear:** one-off purchases contribute revenue, but do not count as recurring subscriptions."
481
+ "markdown": "- **Key rejected:** check the copied value is complete and starts with `rk_live_`.\n- **Permission denied:** check every Read permission above, then reconnect with a corrected key.\n- **MRR is zero but sales appear:** one-off purchases count as revenue, not recurring subscriptions."
482
+ },
483
+ {
484
+ "id": "investigate-with-this-connection",
485
+ "title": "Investigate with this connection",
486
+ "kind": "detail",
487
+ "markdown": "Reads `subscriptions` and `charges` for the key’s merchant account. Filter by price or customer when several products share it. Amounts keep currency and minor units; Stripe SQL and Analytics are not exposed.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source stripe --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
482
488
  },
483
489
  {
484
490
  "id": "sources",
485
491
  "title": "Sources",
486
492
  "kind": "sources",
487
493
  "markdown": "- [Restricted API keys](https://docs.stripe.com/keys/restricted-api-keys)\n- [Analytics API and its write requirement](https://docs.stripe.com/data/analytics)\n- [Subscriptions and charges](https://docs.stripe.com/api)"
488
- },
489
- {
490
- "id": "investigate-with-this-connection",
491
- "title": "Investigate with this connection",
492
- "kind": "detail",
493
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `subscriptions`, `charges`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source stripe --json\n```\n\nCredential-scoped merchant account. Filter by price/customer as appropriate when several products share the account. Amounts retain currency and minor units; SQL/Stripe Analytics is not exposed. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification."
494
494
  }
495
495
  ],
496
- "markdown": "# Stripe\n\n**What Sprid does with this:** Read revenue and subscriptions from your Stripe account.\n\n## You need\n\nPermission to create a **restricted API key** in Stripe. Use a live, read-only key beginning with `rk_live_`.\n\n## Click path (dashboard.stripe.com)\n\n1. Open [Stripe](https://dashboard.stripe.com) → **Developers → API keys → Restricted keys → Create restricted key**.\n2. Name it `Sprid`.\n3. Set these permissions to **Read**:\n - **Core → Charges** and **Account**\n - **Billing → Subscriptions**, **Invoices** and **Prices**\n4. Leave every other permission at **None**.\n5. Create the key, reveal it and save it to a private file such as `~/keys/stripe-sprid.txt`.\n\n## Then run\n\n```\nsprid connect stripe --app <slug> --key ~/keys/stripe-sprid.txt\n```\n\nReplace `<slug>` with your Sprid app slug and use your own file path. Keep the key out of chat.\n\n## How to check it worked\n\nAsk your agent: “Read this app’s Stripe revenue through Sprid and check that it is the right account.” A saved key alone does not confirm access.\n\n## What the numbers mean\n\nMRR estimates current active and past-due subscription prices, spreading annual subscriptions over 12 months. It excludes trials and does not apply discounts, proration or tax, so it differs from Stripe’s dashboard definition. Revenue covers 28 complete calendar days; refunds restate original charge dates. Currencies remain separate.\n\n## If you also use RevenueCat\n\nIf RevenueCat may already count these Stripe sales, Sprid withholds the combined total. Addition requires resolved overlap, matching currencies and compatible measurement definitions.\n\n## If it fails\n\n- **Key rejected:** check the copied value is complete and starts with `rk_live_`.\n- **Permission denied:** check all Read permissions listed above and reconnect with a corrected key.\n- **MRR is zero but sales appear:** one-off purchases contribute revenue, but do not count as recurring subscriptions.\n\n## Sources\n\n- [Restricted API keys](https://docs.stripe.com/keys/restricted-api-keys)\n- [Analytics API and its write requirement](https://docs.stripe.com/data/analytics)\n- [Subscriptions and charges](https://docs.stripe.com/api)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `subscriptions`, `charges`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source stripe --json\n```\n\nCredential-scoped merchant account. Filter by price/customer as appropriate when several products share the account. Amounts retain currency and minor units; SQL/Stripe Analytics is not exposed. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n",
497
- "revision": "de4639297f55afcd"
496
+ "markdown": "# Stripe\n\n**What Sprid does with this:** Read revenue and subscriptions from your Stripe account.\n\n## You need\n\nPermission to create a **restricted API key** in Stripe: live and read-only, beginning with `rk_live_`.\n\n## Click path (dashboard.stripe.com)\n\n1. Open [Stripe](https://dashboard.stripe.com) → **Developers → API keys → Restricted keys → Create restricted key**, and name it `Sprid`.\n2. Set these to **Read**:\n - **Core → Charges** and **Account**\n - **Billing → Subscriptions**, **Invoices** and **Prices**\n3. Leave every other permission at **None**.\n4. Create the key and save it to a private file such as `~/keys/stripe-sprid.txt`.\n\n## Then run\n\n```\nsprid connect stripe --app <slug> --key ~/keys/stripe-sprid.txt\n```\n\nUse your Sprid app slug and your own file path. Keep the key out of chat.\n\n## How to check it worked\n\nAsk your agent: “Read this app’s Stripe revenue through Sprid and check that it is the right account.” A saved key alone does not confirm access.\n\n## What the numbers mean\n\nMRR is current active and past-due subscription prices, with annual plans spread over 12 months. It excludes trials and ignores discounts, proration and tax, so it differs from Stripe’s dashboard. Revenue covers 28 complete calendar days; refunds restate the original charge date. Currencies stay separate.\n\n## If you also use RevenueCat\n\nIf RevenueCat may already count these Stripe sales, Sprid withholds the combined total until the overlap is resolved and currencies and definitions match.\n\n## If it fails\n\n- **Key rejected:** check the copied value is complete and starts with `rk_live_`.\n- **Permission denied:** check every Read permission above, then reconnect with a corrected key.\n- **MRR is zero but sales appear:** one-off purchases count as revenue, not recurring subscriptions.\n\n## Investigate with this connection\n\nReads `subscriptions` and `charges` for the key’s merchant account. Filter by price or customer when several products share it. Amounts keep currency and minor units; Stripe SQL and Analytics are not exposed.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source stripe --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources\n\n- [Restricted API keys](https://docs.stripe.com/keys/restricted-api-keys)\n- [Analytics API and its write requirement](https://docs.stripe.com/data/analytics)\n- [Subscriptions and charges](https://docs.stripe.com/api)\n",
497
+ "revision": "28709aaf752334aa"
498
498
  },
499
499
  {
500
500
  "id": "polar",
@@ -513,13 +513,13 @@ export const GUIDES = [
513
513
  "id": "click-path-polarsh",
514
514
  "title": "Click path (polar.sh)",
515
515
  "kind": "steps",
516
- "markdown": "1. Open your organization → **Settings → Developers**.\n2. Click **New Organization Access Token** and name it `Sprid`.\n3. Select **metrics:read** and leave other permissions off.\n4. Create the token and save it to a private file such as `~/keys/polar-sprid.txt`. It is shown only once."
516
+ "markdown": "1. Open your organization → **Settings → Developers → New Organization Access Token**, and name it `Sprid`.\n2. Select **metrics:read** only.\n3. Create the token and save it to a private file such as `~/keys/polar-sprid.txt`. It is shown once."
517
517
  },
518
518
  {
519
519
  "id": "then-run",
520
520
  "title": "Then run",
521
521
  "kind": "command",
522
- "markdown": "```\nsprid connect polar --app <slug> --key ~/keys/polar-sprid.txt\n```\n\nReplace `<slug>` with your Sprid app slug and use your own file path. If using a personal token that covers several organizations, add `--org <organization-id>` to select yours."
522
+ "markdown": "```\nsprid connect polar --app <slug> --key ~/keys/polar-sprid.txt\n```\n\nUse your Sprid app slug and your own file path. A personal token covering several organizations also needs `--org <organization-id>`."
523
523
  },
524
524
  {
525
525
  "id": "how-to-check-it-worked",
@@ -531,29 +531,29 @@ export const GUIDES = [
531
531
  "id": "what-the-numbers-mean",
532
532
  "title": "What the numbers mean",
533
533
  "kind": "detail",
534
- "markdown": "Sprid shows Polar’s daily revenue and current monthly recurring revenue. Polar does not supply a trial count, so that field stays blank."
534
+ "markdown": "Polar’s daily revenue and current monthly recurring revenue. Polar supplies no trial count, so that field stays blank."
535
535
  },
536
536
  {
537
537
  "id": "if-it-fails",
538
538
  "title": "If it fails",
539
539
  "kind": "troubleshooting",
540
- "markdown": "- **Access denied:** create a replacement token with **metrics:read**, then reconnect.\n- **Wrong or empty results with a personal token:** add `--org <organization-id>` to the connection command."
540
+ "markdown": "- **Access denied:** create a replacement token with **metrics:read**, then reconnect.\n- **Wrong or empty results with a personal token:** add `--org <organization-id>`."
541
+ },
542
+ {
543
+ "id": "investigate-with-this-connection",
544
+ "title": "Investigate with this connection",
545
+ "kind": "detail",
546
+ "markdown": "Reads `metrics`, `orders` and `subscriptions` for the pinned organization. Product filters separate apps sold through the same organization.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source polar --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
541
547
  },
542
548
  {
543
549
  "id": "sources",
544
550
  "title": "Sources",
545
551
  "kind": "sources",
546
552
  "markdown": "- [Metrics endpoint](https://polar.sh/docs/api-reference/metrics/get)"
547
- },
548
- {
549
- "id": "investigate-with-this-connection",
550
- "title": "Investigate with this connection",
551
- "kind": "detail",
552
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `metrics`, `orders`, `subscriptions`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source polar --json\n```\n\nPinned organization. Product filters distinguish apps sold through the same organization. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification."
553
553
  }
554
554
  ],
555
- "markdown": "# Polar\n\n**What Sprid does with this:** Read revenue and subscriptions from your Polar account.\n\n## You need\n\nAccess to your Polar organization’s developer settings.\n\n## Click path (polar.sh)\n\n1. Open your organization → **Settings → Developers**.\n2. Click **New Organization Access Token** and name it `Sprid`.\n3. Select **metrics:read** and leave other permissions off.\n4. Create the token and save it to a private file such as `~/keys/polar-sprid.txt`. It is shown only once.\n\n## Then run\n\n```\nsprid connect polar --app <slug> --key ~/keys/polar-sprid.txt\n```\n\nReplace `<slug>` with your Sprid app slug and use your own file path. If using a personal token that covers several organizations, add `--org <organization-id>` to select yours.\n\n## How to check it worked\n\nAsk your agent: “Check that Sprid can read revenue and subscriptions for this Polar organization.” A saved token alone does not confirm access.\n\n## What the numbers mean\n\nSprid shows Polar’s daily revenue and current monthly recurring revenue. Polar does not supply a trial count, so that field stays blank.\n\n## If it fails\n\n- **Access denied:** create a replacement token with **metrics:read**, then reconnect.\n- **Wrong or empty results with a personal token:** add `--org <organization-id>` to the connection command.\n\n## Sources\n\n- [Metrics endpoint](https://polar.sh/docs/api-reference/metrics/get)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `metrics`, `orders`, `subscriptions`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source polar --json\n```\n\nPinned organization. Product filters distinguish apps sold through the same organization. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n",
556
- "revision": "6e5eccb904cf1679"
555
+ "markdown": "# Polar\n\n**What Sprid does with this:** Read revenue and subscriptions from your Polar account.\n\n## You need\n\nAccess to your Polar organization’s developer settings.\n\n## Click path (polar.sh)\n\n1. Open your organization → **Settings → Developers → New Organization Access Token**, and name it `Sprid`.\n2. Select **metrics:read** only.\n3. Create the token and save it to a private file such as `~/keys/polar-sprid.txt`. It is shown once.\n\n## Then run\n\n```\nsprid connect polar --app <slug> --key ~/keys/polar-sprid.txt\n```\n\nUse your Sprid app slug and your own file path. A personal token covering several organizations also needs `--org <organization-id>`.\n\n## How to check it worked\n\nAsk your agent: “Check that Sprid can read revenue and subscriptions for this Polar organization.” A saved token alone does not confirm access.\n\n## What the numbers mean\n\nPolar’s daily revenue and current monthly recurring revenue. Polar supplies no trial count, so that field stays blank.\n\n## If it fails\n\n- **Access denied:** create a replacement token with **metrics:read**, then reconnect.\n- **Wrong or empty results with a personal token:** add `--org <organization-id>`.\n\n## Investigate with this connection\n\nReads `metrics`, `orders` and `subscriptions` for the pinned organization. Product filters separate apps sold through the same organization.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source polar --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources\n\n- [Metrics endpoint](https://polar.sh/docs/api-reference/metrics/get)\n",
556
+ "revision": "c5764131bc3591a9"
557
557
  },
558
558
  {
559
559
  "id": "lemonsqueezy",
@@ -572,13 +572,13 @@ export const GUIDES = [
572
572
  "id": "click-path-applemonsqueezycom",
573
573
  "title": "Click path (app.lemonsqueezy.com)",
574
574
  "kind": "steps",
575
- "markdown": "1. Open **Settings → API** and click **+** to create a key. Name it `Sprid`.\n2. Copy the key and save it to a private file such as `~/keys/lemonsqueezy-sprid.txt`. It is shown only once.\n3. Open **Settings → Stores**, select your store and copy its numeric id from the page address."
575
+ "markdown": "1. **Settings → API**: click **+** and name the key `Sprid`.\n2. Save the key to a private file such as `~/keys/lemonsqueezy-sprid.txt`. It is shown once.\n3. **Settings → Stores**: select your store and copy its numeric id from the page address."
576
576
  },
577
577
  {
578
578
  "id": "then-run",
579
579
  "title": "Then run",
580
580
  "kind": "command",
581
- "markdown": "```\nsprid connect lemonsqueezy --app <slug> --key ~/keys/lemonsqueezy-sprid.txt --store 12345\n```\n\nReplace `<slug>` with your Sprid app slug. Use your own file path and store id; both are required."
581
+ "markdown": "```\nsprid connect lemonsqueezy --app <slug> --key ~/keys/lemonsqueezy-sprid.txt --store 12345\n```\n\nUse your Sprid app slug, your own file path and your store id. Both `--key` and `--store` are required."
582
582
  },
583
583
  {
584
584
  "id": "how-to-check-it-worked",
@@ -590,29 +590,29 @@ export const GUIDES = [
590
590
  "id": "what-the-numbers-mean",
591
591
  "title": "What the numbers mean",
592
592
  "kind": "detail",
593
- "markdown": "Revenue covers 28 complete calendar days. Sprid combines orders with renewal/update invoices, excludes duplicate initial invoices and deducts refunds on the original purchase date. Currencies remain separate. Incomplete pagination makes the total unavailable.\n\nMRR is unavailable until a complete priced billing schedule can be established; subscription objects supply price IDs rather than the prices needed for that calculation."
593
+ "markdown": "Revenue covers 28 complete calendar days: orders plus renewal and update invoices, without duplicate initial invoices, with refunds deducted on the original purchase date. Currencies stay separate. Incomplete pagination makes the total unavailable.\n\nMRR stays unavailable until a complete priced billing schedule exists, because subscriptions carry price IDs, not prices."
594
594
  },
595
595
  {
596
596
  "id": "if-it-fails",
597
597
  "title": "If it fails",
598
598
  "kind": "troubleshooting",
599
- "markdown": "- **Key rejected:** create a replacement and reconnect.\n- **Store not found:** check the store id belongs to the account that created the key.\n- **An unexpected error page appears:** retry the connection. If it continues, send the message to [Sprid support](mailto:hello@sprid.studio)."
599
+ "markdown": "- **Key rejected:** create a replacement and reconnect.\n- **Store not found:** check the store id belongs to the account that created the key.\n- **An unexpected error page appears:** retry. If it persists, send the message to [Sprid support](mailto:hello@sprid.studio)."
600
+ },
601
+ {
602
+ "id": "investigate-with-this-connection",
603
+ "title": "Investigate with this connection",
604
+ "kind": "detail",
605
+ "markdown": "Reads `orders`, `subscriptions` and `subscription-invoices` for the pinned store. Product and variant filters separate apps in the same store.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source lemonsqueezy --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
600
606
  },
601
607
  {
602
608
  "id": "sources",
603
609
  "title": "Sources",
604
610
  "kind": "sources",
605
611
  "markdown": "- [The store object](https://docs.lemonsqueezy.com/api/stores/the-store-object)\n- [Getting started with the API](https://docs.lemonsqueezy.com/guides/developer-guide/getting-started)"
606
- },
607
- {
608
- "id": "investigate-with-this-connection",
609
- "title": "Investigate with this connection",
610
- "kind": "detail",
611
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `orders`, `subscriptions` and `subscription-invoices`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source lemonsqueezy --json\n```\n\nPinned store. Product/variant filters distinguish apps in the same store. Preserve provider amounts and currencies. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification."
612
612
  }
613
613
  ],
614
- "markdown": "# Lemon Squeezy\n\n**What Sprid does with this:** Read sales and subscriptions from your Lemon Squeezy store.\n\n## You need\n\nAccess to your store’s **Settings → API** page.\n\n## Click path (app.lemonsqueezy.com)\n\n1. Open **Settings → API** and click **+** to create a key. Name it `Sprid`.\n2. Copy the key and save it to a private file such as `~/keys/lemonsqueezy-sprid.txt`. It is shown only once.\n3. Open **Settings → Stores**, select your store and copy its numeric id from the page address.\n\n## Then run\n\n```\nsprid connect lemonsqueezy --app <slug> --key ~/keys/lemonsqueezy-sprid.txt --store 12345\n```\n\nReplace `<slug>` with your Sprid app slug. Use your own file path and store id; both are required.\n\n## How to check it worked\n\nAsk your agent: “Read this store’s Lemon Squeezy sales through Sprid and confirm the store is correct.” A saved key alone does not confirm access.\n\n## What the numbers mean\n\nRevenue covers 28 complete calendar days. Sprid combines orders with renewal/update invoices, excludes duplicate initial invoices and deducts refunds on the original purchase date. Currencies remain separate. Incomplete pagination makes the total unavailable.\n\nMRR is unavailable until a complete priced billing schedule can be established; subscription objects supply price IDs rather than the prices needed for that calculation.\n\n## If it fails\n\n- **Key rejected:** create a replacement and reconnect.\n- **Store not found:** check the store id belongs to the account that created the key.\n- **An unexpected error page appears:** retry the connection. If it continues, send the message to [Sprid support](mailto:hello@sprid.studio).\n\n## Sources\n\n- [The store object](https://docs.lemonsqueezy.com/api/stores/the-store-object)\n- [Getting started with the API](https://docs.lemonsqueezy.com/guides/developer-guide/getting-started)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `orders`, `subscriptions` and `subscription-invoices`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source lemonsqueezy --json\n```\n\nPinned store. Product/variant filters distinguish apps in the same store. Preserve provider amounts and currencies. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n",
615
- "revision": "119ac2c9d6a9737b"
614
+ "markdown": "# Lemon Squeezy\n\n**What Sprid does with this:** Read sales and subscriptions from your Lemon Squeezy store.\n\n## You need\n\nAccess to your store’s **Settings → API** page.\n\n## Click path (app.lemonsqueezy.com)\n\n1. **Settings → API**: click **+** and name the key `Sprid`.\n2. Save the key to a private file such as `~/keys/lemonsqueezy-sprid.txt`. It is shown once.\n3. **Settings → Stores**: select your store and copy its numeric id from the page address.\n\n## Then run\n\n```\nsprid connect lemonsqueezy --app <slug> --key ~/keys/lemonsqueezy-sprid.txt --store 12345\n```\n\nUse your Sprid app slug, your own file path and your store id. Both `--key` and `--store` are required.\n\n## How to check it worked\n\nAsk your agent: “Read this store’s Lemon Squeezy sales through Sprid and confirm the store is correct.” A saved key alone does not confirm access.\n\n## What the numbers mean\n\nRevenue covers 28 complete calendar days: orders plus renewal and update invoices, without duplicate initial invoices, with refunds deducted on the original purchase date. Currencies stay separate. Incomplete pagination makes the total unavailable.\n\nMRR stays unavailable until a complete priced billing schedule exists, because subscriptions carry price IDs, not prices.\n\n## If it fails\n\n- **Key rejected:** create a replacement and reconnect.\n- **Store not found:** check the store id belongs to the account that created the key.\n- **An unexpected error page appears:** retry. If it persists, send the message to [Sprid support](mailto:hello@sprid.studio).\n\n## Investigate with this connection\n\nReads `orders`, `subscriptions` and `subscription-invoices` for the pinned store. Product and variant filters separate apps in the same store.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source lemonsqueezy --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources\n\n- [The store object](https://docs.lemonsqueezy.com/api/stores/the-store-object)\n- [Getting started with the API](https://docs.lemonsqueezy.com/guides/developer-guide/getting-started)\n",
615
+ "revision": "0554f12559c62444"
616
616
  },
617
617
  {
618
618
  "id": "paddle",
@@ -625,19 +625,19 @@ export const GUIDES = [
625
625
  "id": "you-need",
626
626
  "title": "You need",
627
627
  "kind": "requirements",
628
- "markdown": "Access to **Paddle Billing**. Paddle Classic keys cannot be used for this connection. Know whether you are using your live account or sandbox."
628
+ "markdown": "**Paddle Billing** access (Paddle Classic keys do not work), and whether the key is for your live account or sandbox."
629
629
  },
630
630
  {
631
631
  "id": "click-path-vendorspaddlecom",
632
632
  "title": "Click path (vendors.paddle.com)",
633
633
  "kind": "steps",
634
- "markdown": "1. Open **Developer tools → Authentication → New API key** and name it `Sprid`.\n2. Select **transaction.read** and **subscription.read**. Leave other permissions off.\n3. Create the key and save it to a private file such as `~/keys/paddle-sprid.txt`. It is shown only once."
634
+ "markdown": "1. **Developer tools → Authentication → New API key**, named `Sprid`.\n2. Select **transaction.read** and **subscription.read** only.\n3. Create the key and save it to a private file such as `~/keys/paddle-sprid.txt`. It is shown once."
635
635
  },
636
636
  {
637
637
  "id": "then-run",
638
638
  "title": "Then run",
639
639
  "kind": "command",
640
- "markdown": "```\nsprid connect paddle --app <slug> --key ~/keys/paddle-sprid.txt\n```\n\nReplace `<slug>` with your Sprid app slug and use your own file path. For a sandbox key, add `--env sandbox`."
640
+ "markdown": "```\nsprid connect paddle --app <slug> --key ~/keys/paddle-sprid.txt\n```\n\nUse your Sprid app slug and your own file path. Add `--env sandbox` for a sandbox key."
641
641
  },
642
642
  {
643
643
  "id": "how-to-check-it-worked",
@@ -649,29 +649,29 @@ export const GUIDES = [
649
649
  "id": "what-the-numbers-mean",
650
650
  "title": "What the numbers mean",
651
651
  "kind": "detail",
652
- "markdown": "Revenue covers 28 complete days of gross completed transactions, including tax and before refunds, credits, chargebacks and Paddle’s fees. It will differ from your payout. MRR estimates current active subscription prices over their billing periods. Currencies remain separate; incomplete pagination makes a total unavailable."
652
+ "markdown": "Revenue covers 28 complete days of gross completed transactions: tax included, before refunds, credits, chargebacks and Paddle’s fees, so it will exceed your payout. MRR is current active subscription prices over their billing periods. Currencies stay separate; incomplete pagination makes a total unavailable."
653
653
  },
654
654
  {
655
655
  "id": "if-it-fails",
656
656
  "title": "If it fails",
657
657
  "kind": "troubleshooting",
658
- "markdown": "- **Access denied:** check whether the key is for sandbox. If so, reconnect with `--env sandbox`.\n- **A permission is missing:** create a replacement key with both permissions above, then reconnect.\n- **Revenue exceeds your payout:** compare customer payments, rather than the amount left after tax and fees."
658
+ "markdown": "- **Access denied:** a sandbox key needs `--env sandbox`.\n- **A permission is missing:** create a replacement key with both permissions, then reconnect.\n- **Revenue exceeds your payout:** expected. Compare against customer payments, not what is left after tax and fees."
659
+ },
660
+ {
661
+ "id": "investigate-with-this-connection",
662
+ "title": "Investigate with this connection",
663
+ "kind": "detail",
664
+ "markdown": "Reads `transactions` and `subscriptions` for the key’s merchant account, live or sandbox as saved. Amounts are minor-unit strings. Subscription creation-date filters apply per page, so keep paginating past pages with no matches.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source paddle --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
659
665
  },
660
666
  {
661
667
  "id": "sources",
662
668
  "title": "Sources",
663
669
  "kind": "sources",
664
670
  "markdown": "- [List transactions](https://developer.paddle.com/api-reference/transactions/list-transactions)\n- [API authentication](https://developer.paddle.com/api-reference/about/authentication)"
665
- },
666
- {
667
- "id": "investigate-with-this-connection",
668
- "title": "Investigate with this connection",
669
- "kind": "detail",
670
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `transactions`, `subscriptions`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source paddle --json\n```\n\nCredential-scoped merchant account, with sandbox/live from the profile. Dates filter billed or created time as described; monetary values are minor-unit strings. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n\nSubscription investigations need `subscription.read` in addition to `transaction.read` for transaction pages. Creation-date filtering on subscriptions happens per page in Sprid; continue pagination even if a page contains no matches."
671
671
  }
672
672
  ],
673
- "markdown": "# Paddle\n\n**What Sprid does with this:** Read revenue and subscriptions from your Paddle account.\n\n## You need\n\nAccess to **Paddle Billing**. Paddle Classic keys cannot be used for this connection. Know whether you are using your live account or sandbox.\n\n## Click path (vendors.paddle.com)\n\n1. Open **Developer tools → Authentication → New API key** and name it `Sprid`.\n2. Select **transaction.read** and **subscription.read**. Leave other permissions off.\n3. Create the key and save it to a private file such as `~/keys/paddle-sprid.txt`. It is shown only once.\n\n## Then run\n\n```\nsprid connect paddle --app <slug> --key ~/keys/paddle-sprid.txt\n```\n\nReplace `<slug>` with your Sprid app slug and use your own file path. For a sandbox key, add `--env sandbox`.\n\n## How to check it worked\n\nAsk your agent: “Check that Sprid can read this Paddle account’s completed sales and active subscriptions.” A saved key alone does not confirm access.\n\n## What the numbers mean\n\nRevenue covers 28 complete days of gross completed transactions, including tax and before refunds, credits, chargebacks and Paddle’s fees. It will differ from your payout. MRR estimates current active subscription prices over their billing periods. Currencies remain separate; incomplete pagination makes a total unavailable.\n\n## If it fails\n\n- **Access denied:** check whether the key is for sandbox. If so, reconnect with `--env sandbox`.\n- **A permission is missing:** create a replacement key with both permissions above, then reconnect.\n- **Revenue exceeds your payout:** compare customer payments, rather than the amount left after tax and fees.\n\n## Sources\n\n- [List transactions](https://developer.paddle.com/api-reference/transactions/list-transactions)\n- [API authentication](https://developer.paddle.com/api-reference/about/authentication)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `transactions`, `subscriptions`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source paddle --json\n```\n\nCredential-scoped merchant account, with sandbox/live from the profile. Dates filter billed or created time as described; monetary values are minor-unit strings. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n\nSubscription investigations need `subscription.read` in addition to `transaction.read` for transaction pages. Creation-date filtering on subscriptions happens per page in Sprid; continue pagination even if a page contains no matches.\n",
674
- "revision": "8aa94285843990a3"
673
+ "markdown": "# Paddle\n\n**What Sprid does with this:** Read revenue and subscriptions from your Paddle account.\n\n## You need\n\n**Paddle Billing** access (Paddle Classic keys do not work), and whether the key is for your live account or sandbox.\n\n## Click path (vendors.paddle.com)\n\n1. **Developer tools → Authentication → New API key**, named `Sprid`.\n2. Select **transaction.read** and **subscription.read** only.\n3. Create the key and save it to a private file such as `~/keys/paddle-sprid.txt`. It is shown once.\n\n## Then run\n\n```\nsprid connect paddle --app <slug> --key ~/keys/paddle-sprid.txt\n```\n\nUse your Sprid app slug and your own file path. Add `--env sandbox` for a sandbox key.\n\n## How to check it worked\n\nAsk your agent: “Check that Sprid can read this Paddle account’s completed sales and active subscriptions.” A saved key alone does not confirm access.\n\n## What the numbers mean\n\nRevenue covers 28 complete days of gross completed transactions: tax included, before refunds, credits, chargebacks and Paddle’s fees, so it will exceed your payout. MRR is current active subscription prices over their billing periods. Currencies stay separate; incomplete pagination makes a total unavailable.\n\n## If it fails\n\n- **Access denied:** a sandbox key needs `--env sandbox`.\n- **A permission is missing:** create a replacement key with both permissions, then reconnect.\n- **Revenue exceeds your payout:** expected. Compare against customer payments, not what is left after tax and fees.\n\n## Investigate with this connection\n\nReads `transactions` and `subscriptions` for the key’s merchant account, live or sandbox as saved. Amounts are minor-unit strings. Subscription creation-date filters apply per page, so keep paginating past pages with no matches.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source paddle --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources\n\n- [List transactions](https://developer.paddle.com/api-reference/transactions/list-transactions)\n- [API authentication](https://developer.paddle.com/api-reference/about/authentication)\n",
674
+ "revision": "f02048423594cb56"
675
675
  },
676
676
  {
677
677
  "id": "cloudflare",
@@ -690,53 +690,47 @@ export const GUIDES = [
690
690
  "id": "click-path-dashcloudflarecom",
691
691
  "title": "Click path (dash.cloudflare.com)",
692
692
  "kind": "steps",
693
- "markdown": "1. Open [Cloudflare](https://dash.cloudflare.com) → your profile icon → **My Profile → API Tokens**.\n2. Click **Create Token → Custom token → Get started** and name it `Sprid`.\n3. Add these **Permissions**:\n - **Zone → Analytics → Read**\n - **Account → Account Analytics → Read**\n4. Set **Zone Resources → Include → Specific zone** to your domain.\n5. Set **Account Resources → Include** to your account.\n6. Leave **Client IP Address Filtering** and **TTL** empty. Click **Continue to summary → Create Token**.\n7. Save the token to a private file, such as `~/keys/cloudflare-sprid.txt`. It is shown only once."
693
+ "markdown": "1. Open [Cloudflare](https://dash.cloudflare.com) → profile icon → **My Profile → API Tokens**.\n2. **Create Token → Custom token → Get started**, named `Sprid`.\n3. **Permissions**:\n - **Zone → Analytics → Read**\n - **Account → Account Analytics → Read**\n4. **Zone Resources → Include → Specific zone**: your domain.\n5. **Account Resources → Include**: your account.\n6. Leave **Client IP Address Filtering** and **TTL** empty. **Continue to summary → Create Token**.\n7. Save the token to a private file such as `~/keys/cloudflare-sprid.txt`. It is shown once.\n\nFor a connection that survives you leaving the team, create an account-owned token instead under **Manage Account → Account API Tokens**, with the same permissions."
694
694
  },
695
695
  {
696
696
  "id": "zone-id",
697
697
  "title": "Zone id",
698
698
  "kind": "identifiers",
699
- "markdown": "Open your domain → **Overview** and copy **Zone ID** and **Account ID** from the **API** card. Use the Zone ID below. Ask your agent to save the Account ID in your Sprid app profile."
699
+ "markdown": "Your domain → **Overview** → **API** card: copy **Zone ID** and **Account ID**. The Zone ID goes in the command; ask your agent to save the Account ID in your Sprid app profile."
700
700
  },
701
701
  {
702
702
  "id": "then-run",
703
703
  "title": "Then run",
704
704
  "kind": "command",
705
- "markdown": "```\nsprid connect cloudflare --token ~/keys/cloudflare-sprid.txt --zone 0123456789abcdef0123456789abcdef\n```\n\nReplace the file path and Zone ID with yours. Do not paste the token into chat."
705
+ "markdown": "```\nsprid connect cloudflare --token ~/keys/cloudflare-sprid.txt --zone 0123456789abcdef0123456789abcdef\n```\n\nUse your own file path and Zone ID. Keep the token out of chat."
706
706
  },
707
707
  {
708
708
  "id": "how-to-check-it-worked",
709
709
  "title": "How to check it worked",
710
710
  "kind": "verify",
711
- "markdown": "Run `sprid status`, then ask your agent: “Check that Sprid can read recent Cloudflare traffic for this domain.”\n\nThe check should identify the domain and say whether visitor reports or only request counts are available."
711
+ "markdown": "Run `sprid status`, then ask your agent: “Check that Sprid can read recent Cloudflare traffic for this domain.” The check names the domain and says whether visitor reports or only request counts are available."
712
712
  },
713
713
  {
714
714
  "id": "if-it-fails",
715
715
  "title": "If it fails",
716
716
  "kind": "troubleshooting",
717
- "markdown": "- **Access denied:** open **API Tokens → ⋯ → Edit**. Check both Read permissions and the selected domain and account.\n- **Domain not found:** use the copied Zone ID instead of the domain name.\n- **Visitor reports are empty:** enable **Analytics & Logs → Web Analytics** and check that tracking is installed on your site."
717
+ "markdown": "- **Access denied:** **API Tokens → ⋯ → Edit**. Check both Read permissions and the selected domain and account.\n- **Domain not found:** use the Zone ID, not the domain name.\n- **Visitor reports are empty:** enable **Analytics & Logs → Web Analytics** and check the tracking snippet is on your site."
718
718
  },
719
719
  {
720
- "id": "token-ownership-and-analytics-permissions",
721
- "title": "Token ownership and analytics permissions",
720
+ "id": "investigate-with-this-connection",
721
+ "title": "Investigate with this connection",
722
722
  "kind": "detail",
723
- "markdown": "For a connection that stays active when you leave the team, create an account-owned token under **Manage Account → Account API Tokens** with the same permissions."
723
+ "markdown": "Reads `rum` and `http`. RUM is pinned to the saved account and hostname; beacon traffic is sampled and not verified human.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source cloudflare --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
724
724
  },
725
725
  {
726
726
  "id": "sources",
727
727
  "title": "Sources",
728
728
  "kind": "sources",
729
729
  "markdown": "- [Create an API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/)\n- [GraphQL Analytics API token permissions](https://developers.cloudflare.com/analytics/graphql-api/getting-started/authentication/api-token-auth/)\n- [Find zone and account ids](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/)\n- [Verify a token](https://developers.cloudflare.com/api/resources/user/subresources/tokens/methods/verify/)"
730
- },
731
- {
732
- "id": "investigate-with-this-connection",
733
- "title": "Investigate with this connection",
734
- "kind": "detail",
735
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `rum`, `http`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source cloudflare --json\n```\n\nRUM is pinned to the saved account and website hostname. Beacon traffic is sampled and is not verified human traffic. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification."
736
730
  }
737
731
  ],
738
- "markdown": "# Cloudflare (optional)\n\n**What Sprid does with this:** Read traffic reports for your website.\n\n## You need\n\nAccess to your domain’s Cloudflare account and permission to create a token. Enable **Web Analytics** for visitor reports; basic request counts include bots.\n\n## Click path (dash.cloudflare.com)\n\n1. Open [Cloudflare](https://dash.cloudflare.com) → your profile icon → **My Profile → API Tokens**.\n2. Click **Create Token → Custom token → Get started** and name it `Sprid`.\n3. Add these **Permissions**:\n - **Zone → Analytics → Read**\n - **Account → Account Analytics → Read**\n4. Set **Zone Resources → Include → Specific zone** to your domain.\n5. Set **Account Resources → Include** to your account.\n6. Leave **Client IP Address Filtering** and **TTL** empty. Click **Continue to summary → Create Token**.\n7. Save the token to a private file, such as `~/keys/cloudflare-sprid.txt`. It is shown only once.\n\n## Zone id\n\nOpen your domain → **Overview** and copy **Zone ID** and **Account ID** from the **API** card. Use the Zone ID below. Ask your agent to save the Account ID in your Sprid app profile.\n\n## Then run\n\n```\nsprid connect cloudflare --token ~/keys/cloudflare-sprid.txt --zone 0123456789abcdef0123456789abcdef\n```\n\nReplace the file path and Zone ID with yours. Do not paste the token into chat.\n\n## How to check it worked\n\nRun `sprid status`, then ask your agent: “Check that Sprid can read recent Cloudflare traffic for this domain.”\n\nThe check should identify the domain and say whether visitor reports or only request counts are available.\n\n## If it fails\n\n- **Access denied:** open **API Tokens → ⋯ → Edit**. Check both Read permissions and the selected domain and account.\n- **Domain not found:** use the copied Zone ID instead of the domain name.\n- **Visitor reports are empty:** enable **Analytics & Logs → Web Analytics** and check that tracking is installed on your site.\n\n## Token ownership and analytics permissions\n\nFor a connection that stays active when you leave the team, create an account-owned token under **Manage Account → Account API Tokens** with the same permissions.\n\n## Sources\n\n- [Create an API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/)\n- [GraphQL Analytics API token permissions](https://developers.cloudflare.com/analytics/graphql-api/getting-started/authentication/api-token-auth/)\n- [Find zone and account ids](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/)\n- [Verify a token](https://developers.cloudflare.com/api/resources/user/subresources/tokens/methods/verify/)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `rum`, `http`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source cloudflare --json\n```\n\nRUM is pinned to the saved account and website hostname. Beacon traffic is sampled and is not verified human traffic. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n",
739
- "revision": "271012aedf50a897"
732
+ "markdown": "# Cloudflare (optional)\n\n**What Sprid does with this:** Read traffic reports for your website.\n\n## You need\n\nAccess to your domain’s Cloudflare account and permission to create a token. Enable **Web Analytics** for visitor reports; basic request counts include bots.\n\n## Click path (dash.cloudflare.com)\n\n1. Open [Cloudflare](https://dash.cloudflare.com) → profile icon → **My Profile → API Tokens**.\n2. **Create Token → Custom token → Get started**, named `Sprid`.\n3. **Permissions**:\n - **Zone → Analytics → Read**\n - **Account → Account Analytics → Read**\n4. **Zone Resources → Include → Specific zone**: your domain.\n5. **Account Resources → Include**: your account.\n6. Leave **Client IP Address Filtering** and **TTL** empty. **Continue to summary → Create Token**.\n7. Save the token to a private file such as `~/keys/cloudflare-sprid.txt`. It is shown once.\n\nFor a connection that survives you leaving the team, create an account-owned token instead under **Manage Account → Account API Tokens**, with the same permissions.\n\n## Zone id\n\nYour domain → **Overview** → **API** card: copy **Zone ID** and **Account ID**. The Zone ID goes in the command; ask your agent to save the Account ID in your Sprid app profile.\n\n## Then run\n\n```\nsprid connect cloudflare --token ~/keys/cloudflare-sprid.txt --zone 0123456789abcdef0123456789abcdef\n```\n\nUse your own file path and Zone ID. Keep the token out of chat.\n\n## How to check it worked\n\nRun `sprid status`, then ask your agent: “Check that Sprid can read recent Cloudflare traffic for this domain.” The check names the domain and says whether visitor reports or only request counts are available.\n\n## If it fails\n\n- **Access denied:** **API Tokens → ⋯ → Edit**. Check both Read permissions and the selected domain and account.\n- **Domain not found:** use the Zone ID, not the domain name.\n- **Visitor reports are empty:** enable **Analytics & Logs → Web Analytics** and check the tracking snippet is on your site.\n\n## Investigate with this connection\n\nReads `rum` and `http`. RUM is pinned to the saved account and hostname; beacon traffic is sampled and not verified human.\n\n```sh\nsprid marketing-review capabilities --app <slug> --source cloudflare --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources\n\n- [Create an API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/)\n- [GraphQL Analytics API token permissions](https://developers.cloudflare.com/analytics/graphql-api/getting-started/authentication/api-token-auth/)\n- [Find zone and account ids](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/)\n- [Verify a token](https://developers.cloudflare.com/api/resources/user/subresources/tokens/methods/verify/)\n",
733
+ "revision": "0465290022d3254f"
740
734
  },
741
735
  {
742
736
  "id": "instagram",
@@ -749,47 +743,47 @@ export const GUIDES = [
749
743
  "id": "you-need",
750
744
  "title": "You need",
751
745
  "kind": "requirements",
752
- "markdown": "Creating a brand, creator or side account? Start with [Create your social accounts](https://sprid.studio/docs/connect/social-accounts).\n\n- An Instagram **Business** or **Creator** account. A Facebook Page is optional.\n- Your phone for any two-factor sign-in prompt.\n\n### Create an account first\n\nOpen [Instagram signup](https://www.instagram.com/accounts/emailsignup/), enter the intended owner email and chosen handle, then complete verification yourself. Check the resulting profile before connecting it.\n\n### Switch a personal account to professional\n\nIn Instagram, open your profile → **≡ → Settings and activity → For professionals → Account type and tools → Switch to professional account**. Pick a category, then **Creator** or **Business**. You can skip linking a Facebook Page."
746
+ "markdown": "- An Instagram **Business** or **Creator** account. A Facebook Page is optional. No account yet? Start with [Create your social accounts](https://sprid.studio/docs/connect/social-accounts).\n- Your phone for any two-factor prompt.\n\nTo switch a personal account: profile → **≡ → Settings and activity → For professionals → Account type and tools → Switch to professional account** → pick a category → **Creator** or **Business**. You can skip linking a Facebook Page."
753
747
  },
754
748
  {
755
749
  "id": "then-run",
756
750
  "title": "Then run",
757
751
  "kind": "command",
758
- "markdown": "```\nsprid connect instagram --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug. You can also connect from **Settings → Channels → Connect Instagram** in Sprid."
752
+ "markdown": "```\nsprid connect instagram --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug, or use **Settings → Channels → Connect Instagram** in Sprid. In an agent-controlled browser, add `--no-browser` and open the returned link in the session for the intended account. Keep that link private."
759
753
  },
760
754
  {
761
755
  "id": "click-path-the-connect",
762
756
  "title": "Click path (the connect)",
763
757
  "kind": "steps",
764
- "markdown": "1. In the browser that opens, sign in to the Instagram account you want to use. If the wrong account is signed in, sign out first.\n2. Review the access Sprid requests for publishing and reading results. Leave the requested permissions enabled.\n3. Click **Allow** to return to Sprid.\n\nFor an agent-controlled browser session, add `--no-browser` to the command and open its returned authorization link in the session for the intended Instagram account. Check the handle in the consent dialog before selecting Allow. Keep the temporary link private."
758
+ "markdown": "1. Sign in to the Instagram account you want. If another account is signed in, sign out first.\n2. Check the handle in the consent dialog and leave the requested permissions enabled.\n3. Click **Allow** to return to Sprid."
765
759
  },
766
760
  {
767
761
  "id": "how-to-check-it-worked",
768
762
  "title": "How to check it worked",
769
763
  "kind": "verify",
770
- "markdown": "Run `sprid status` and check that Instagram shows the right handle as connected. Before scheduling a batch, publish a post you have reviewed and check it on Instagram."
764
+ "markdown": "Run `sprid status` and check that Instagram shows the right handle. Before scheduling a batch, publish one reviewed post and check it on Instagram."
771
765
  },
772
766
  {
773
767
  "id": "if-it-fails",
774
768
  "title": "If it fails",
775
769
  "kind": "troubleshooting",
776
- "markdown": "- **Account not eligible:** switch to Business or Creator, then reconnect.\n- **Already connected elsewhere:** first check the handle. If another account is signed in, cancel and restart with the correct login. Move a connection only when you intend to change its Sprid destination.\n- **Connection expired or permissions missing:** run the connect command again and approve the requested access.\n- **App unavailable, “Invalid Scopes” or a redirect error:** contact [Sprid support](mailto:hello@sprid.studio) with the error message. Sprid needs to resolve this; changing your account will not help.\n\n- **Long-lived token exchange fails after Allow:** authorization has not completed. Contact [Sprid support](mailto:hello@sprid.studio) with the error text. Sprid must check app access and any tester-role requirement. If Sprid invites your account, open Instagram Settings → Website permissions → Apps and websites → Tester Invites, accept Sprid-IG, then start a fresh connection. The Tester Invites tab may appear only after an invitation; do not create another Instagram account or your own Meta developer app. An error such as `Unsupported request - method type: get` does not, by itself, identify the cause."
770
+ "markdown": "- **Account not eligible:** switch to Business or Creator, then reconnect.\n- **Already connected elsewhere:** check the handle. If the wrong account was signed in, cancel and restart. Move a connection only if you mean to change its Sprid destination.\n- **Connection expired or permissions missing:** run the connect command again and approve all requested access.\n- **App unavailable, “Invalid Scopes” or a redirect error:** send the error to [Sprid support](mailto:hello@sprid.studio). Only Sprid can fix these.\n- **Long-lived token exchange fails after Allow:** authorization did not complete. Send the error text to [Sprid support](mailto:hello@sprid.studio). If Sprid invites your account as a tester, open Instagram **Settings → Website permissions → Apps and websites → Tester Invites**, accept Sprid-IG, then start a fresh connection. The tab may only appear after an invitation. Do not create another Instagram account or your own Meta developer app. `Unsupported request - method type: get` alone does not identify the cause."
771
+ },
772
+ {
773
+ "id": "investigate-with-this-connection",
774
+ "title": "Investigate with this connection",
775
+ "kind": "detail",
776
+ "markdown": "Operations `posts` and `comments`, read from Sprid’s stored publishes, metric snapshots and inbox (no live platform read; missing metrics are unmeasured, not zero).\n\n```sh\nsprid marketing-review capabilities --app <slug> --source instagram --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
777
777
  },
778
778
  {
779
779
  "id": "sources",
780
780
  "title": "Sources",
781
781
  "kind": "sources",
782
782
  "markdown": "- [Instagram API with Instagram Login](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/)\n- [Business Login for Instagram](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/business-login)\n- [Permission descriptions and App Review requirement](https://developers.facebook.com/docs/permissions)\n- [App modes](https://developers.facebook.com/docs/development/build-and-test/app-modes)\n- [Switch to a professional account](https://creatorsupport.creatoriq.com/hc/en-us/articles/13578083261709-How-do-I-switch-from-a-Personal-to-a-Professional-Instagram-account)"
783
- },
784
- {
785
- "id": "investigate-with-this-connection",
786
- "title": "Investigate with this connection",
787
- "kind": "detail",
788
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `posts`, `comments`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source instagram --json\n```\n\nQueries Sprid’s stored publishes, metric snapshots and inbox for the linked content account. No live platform sync or paid API read. Missing metrics are unmeasured; this does not expose the platform’s entire API. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification."
789
783
  }
790
784
  ],
791
- "markdown": "# Instagram\n\n**What Sprid does with this:** Publish approved posts to Instagram and see how they perform.\n\n## You need\n\nCreating a brand, creator or side account? Start with [Create your social accounts](https://sprid.studio/docs/connect/social-accounts).\n\n- An Instagram **Business** or **Creator** account. A Facebook Page is optional.\n- Your phone for any two-factor sign-in prompt.\n\n### Create an account first\n\nOpen [Instagram signup](https://www.instagram.com/accounts/emailsignup/), enter the intended owner email and chosen handle, then complete verification yourself. Check the resulting profile before connecting it.\n\n### Switch a personal account to professional\n\nIn Instagram, open your profile → **≡ → Settings and activity → For professionals → Account type and tools → Switch to professional account**. Pick a category, then **Creator** or **Business**. You can skip linking a Facebook Page.\n\n## Then run\n\n```\nsprid connect instagram --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug. You can also connect from **Settings → Channels → Connect Instagram** in Sprid.\n\n## Click path (the connect)\n\n1. In the browser that opens, sign in to the Instagram account you want to use. If the wrong account is signed in, sign out first.\n2. Review the access Sprid requests for publishing and reading results. Leave the requested permissions enabled.\n3. Click **Allow** to return to Sprid.\n\nFor an agent-controlled browser session, add `--no-browser` to the command and open its returned authorization link in the session for the intended Instagram account. Check the handle in the consent dialog before selecting Allow. Keep the temporary link private.\n\n## How to check it worked\n\nRun `sprid status` and check that Instagram shows the right handle as connected. Before scheduling a batch, publish a post you have reviewed and check it on Instagram.\n\n## If it fails\n\n- **Account not eligible:** switch to Business or Creator, then reconnect.\n- **Already connected elsewhere:** first check the handle. If another account is signed in, cancel and restart with the correct login. Move a connection only when you intend to change its Sprid destination.\n- **Connection expired or permissions missing:** run the connect command again and approve the requested access.\n- **App unavailable, “Invalid Scopes” or a redirect error:** contact [Sprid support](mailto:hello@sprid.studio) with the error message. Sprid needs to resolve this; changing your account will not help.\n\n- **Long-lived token exchange fails after Allow:** authorization has not completed. Contact [Sprid support](mailto:hello@sprid.studio) with the error text. Sprid must check app access and any tester-role requirement. If Sprid invites your account, open Instagram Settings → Website permissions → Apps and websites → Tester Invites, accept Sprid-IG, then start a fresh connection. The Tester Invites tab may appear only after an invitation; do not create another Instagram account or your own Meta developer app. An error such as `Unsupported request - method type: get` does not, by itself, identify the cause.\n\n## Sources\n\n- [Instagram API with Instagram Login](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/)\n- [Business Login for Instagram](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/business-login)\n- [Permission descriptions and App Review requirement](https://developers.facebook.com/docs/permissions)\n- [App modes](https://developers.facebook.com/docs/development/build-and-test/app-modes)\n- [Switch to a professional account](https://creatorsupport.creatoriq.com/hc/en-us/articles/13578083261709-How-do-I-switch-from-a-Personal-to-a-Professional-Instagram-account)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `posts`, `comments`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source instagram --json\n```\n\nQueries Sprid’s stored publishes, metric snapshots and inbox for the linked content account. No live platform sync or paid API read. Missing metrics are unmeasured; this does not expose the platform’s entire API. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n",
792
- "revision": "369429afd80634a2"
785
+ "markdown": "# Instagram\n\n**What Sprid does with this:** Publish approved posts to Instagram and see how they perform.\n\n## You need\n\n- An Instagram **Business** or **Creator** account. A Facebook Page is optional. No account yet? Start with [Create your social accounts](https://sprid.studio/docs/connect/social-accounts).\n- Your phone for any two-factor prompt.\n\nTo switch a personal account: profile → **≡ → Settings and activity → For professionals → Account type and tools → Switch to professional account** → pick a category → **Creator** or **Business**. You can skip linking a Facebook Page.\n\n## Then run\n\n```\nsprid connect instagram --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug, or use **Settings → Channels → Connect Instagram** in Sprid. In an agent-controlled browser, add `--no-browser` and open the returned link in the session for the intended account. Keep that link private.\n\n## Click path (the connect)\n\n1. Sign in to the Instagram account you want. If another account is signed in, sign out first.\n2. Check the handle in the consent dialog and leave the requested permissions enabled.\n3. Click **Allow** to return to Sprid.\n\n## How to check it worked\n\nRun `sprid status` and check that Instagram shows the right handle. Before scheduling a batch, publish one reviewed post and check it on Instagram.\n\n## If it fails\n\n- **Account not eligible:** switch to Business or Creator, then reconnect.\n- **Already connected elsewhere:** check the handle. If the wrong account was signed in, cancel and restart. Move a connection only if you mean to change its Sprid destination.\n- **Connection expired or permissions missing:** run the connect command again and approve all requested access.\n- **App unavailable, “Invalid Scopes” or a redirect error:** send the error to [Sprid support](mailto:hello@sprid.studio). Only Sprid can fix these.\n- **Long-lived token exchange fails after Allow:** authorization did not complete. Send the error text to [Sprid support](mailto:hello@sprid.studio). If Sprid invites your account as a tester, open Instagram **Settings → Website permissions → Apps and websites → Tester Invites**, accept Sprid-IG, then start a fresh connection. The tab may only appear after an invitation. Do not create another Instagram account or your own Meta developer app. `Unsupported request - method type: get` alone does not identify the cause.\n\n## Investigate with this connection\n\nOperations `posts` and `comments`, read from Sprid’s stored publishes, metric snapshots and inbox (no live platform read; missing metrics are unmeasured, not zero).\n\n```sh\nsprid marketing-review capabilities --app <slug> --source instagram --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources\n\n- [Instagram API with Instagram Login](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/)\n- [Business Login for Instagram](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/business-login)\n- [Permission descriptions and App Review requirement](https://developers.facebook.com/docs/permissions)\n- [App modes](https://developers.facebook.com/docs/development/build-and-test/app-modes)\n- [Switch to a professional account](https://creatorsupport.creatoriq.com/hc/en-us/articles/13578083261709-How-do-I-switch-from-a-Personal-to-a-Professional-Instagram-account)\n",
786
+ "revision": "116e5b47d45175e7"
793
787
  },
794
788
  {
795
789
  "id": "tiktok",
@@ -802,7 +796,7 @@ export const GUIDES = [
802
796
  "id": "you-need",
803
797
  "title": "You need",
804
798
  "kind": "requirements",
805
- "markdown": "A TikTok account you can sign in to. Make it public if you want to publish public posts."
799
+ "markdown": "A TikTok account you can sign in to, set to public if you want public posts."
806
800
  },
807
801
  {
808
802
  "id": "then-run",
@@ -814,41 +808,41 @@ export const GUIDES = [
814
808
  "id": "click-path-the-connect",
815
809
  "title": "Click path (the connect)",
816
810
  "kind": "steps",
817
- "markdown": "1. Sign in to TikTok in the browser that opens. You can scan the QR code with your phone.\n2. Review Sprid’s requested access and click **Authorize**.\n3. Return to Sprid and check the connected handle."
811
+ "markdown": "1. Sign in to TikTok in the browser that opens, or scan the QR code with your phone.\n2. Review the requested access and click **Authorize**.\n3. Back in Sprid, check the connected handle."
818
812
  },
819
813
  {
820
814
  "id": "how-to-check-it-worked",
821
815
  "title": "How to check it worked",
822
816
  "kind": "verify",
823
- "markdown": "Run `sprid status` and check your TikTok handle. For a publishing check, choose **Only me** for a reviewed post, then find it on your TikTok profile."
817
+ "markdown": "Run `sprid status` and check the handle. To test publishing, post a reviewed post as **Only me** and find it on your profile."
824
818
  },
825
819
  {
826
820
  "id": "before-publishing",
827
821
  "title": "Before publishing",
828
822
  "kind": "detail",
829
- "markdown": "Choose the audience and whether to allow comments each time. Videos also offer Duet and Stitch where your account allows them.\n\nIf a post promotes your business or another brand, complete the commercial-content disclosure. Branded content cannot use **Only me**. Review the music and branded-content terms above the publish button.\n\nThese choices apply to every post shown in a batch. Rescheduling an existing booking keeps that booking’s choices."
823
+ "markdown": "TikTok requires you to choose these on every post, with nothing pre-filled:\n\n- Audience (privacy) and whether to allow comments. Videos also offer Duet and Stitch where your account allows them.\n- The commercial-content disclosure, if the post promotes your business or another brand. Branded content cannot use **Only me**.\n- The music and branded-content terms shown above the publish button.\n\nIn a batch, your choices apply to every post shown. Rescheduling a booking keeps its original choices."
830
824
  },
831
825
  {
832
826
  "id": "if-it-fails",
833
827
  "title": "If it fails",
834
828
  "kind": "troubleshooting",
835
- "markdown": "- **A public post arrives as private:** use **Send as a draft** and finish publishing in TikTok. Contact [Sprid support](mailto:hello@sprid.studio) about the public-posting restriction.\n- **A publishing limit is reached:** retry later, or use the draft option if available.\n- **“Checking your TikTok account…” stays on screen:** select **Retry**. If it still fails, reconnect.\n- **Publish stays disabled:** complete the audience and disclosure choices and resolve any message shown beside the button."
829
+ "markdown": "- **A public post arrives as private:** use **Send as a draft** and finish in TikTok, and tell [Sprid support](mailto:hello@sprid.studio) about the public-posting restriction.\n- **Publishing limit reached:** retry later, or send as a draft.\n- **“Checking your TikTok account…” stays on screen:** select **Retry**, then reconnect if it still fails.\n- **Publish stays disabled:** complete the audience and disclosure choices and resolve any message beside the button."
830
+ },
831
+ {
832
+ "id": "investigate-with-this-connection",
833
+ "title": "Investigate with this connection",
834
+ "kind": "detail",
835
+ "markdown": "Operations `posts` and `comments`, read from Sprid’s stored publishes, metric snapshots and inbox (no live platform read; missing metrics are unmeasured, not zero).\n\n```sh\nsprid marketing-review capabilities --app <slug> --source tiktok --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
836
836
  },
837
837
  {
838
838
  "id": "sources",
839
839
  "title": "Sources",
840
840
  "kind": "sources",
841
841
  "markdown": "- [Content Posting API](https://developers.tiktok.com/doc/content-posting-api-get-started)\n- [Direct Post reference](https://developers.tiktok.com/doc/content-posting-api-reference-direct-post)\n- [Query creator info](https://developers.tiktok.com/doc/content-posting-api-reference-query-creator-info)\n- [Content Sharing Guidelines](https://developers.tiktok.com/doc/content-sharing-guidelines)\n- [Login Kit for web](https://developers.tiktok.com/doc/login-kit-web)"
842
- },
843
- {
844
- "id": "investigate-with-this-connection",
845
- "title": "Investigate with this connection",
846
- "kind": "detail",
847
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `posts`, `comments`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source tiktok --json\n```\n\nQueries Sprid’s stored publishes, metric snapshots and inbox for the linked content account. No live platform sync or paid API read. Missing metrics are unmeasured; this does not expose the platform’s entire API. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification."
848
842
  }
849
843
  ],
850
- "markdown": "# TikTok\n\n**What Sprid does with this:** Publish approved posts to TikTok, or send drafts to finish in the TikTok app.\n\n## You need\n\nA TikTok account you can sign in to. Make it public if you want to publish public posts.\n\n## Then run\n\n```\nsprid connect tiktok --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug.\n\n## Click path (the connect)\n\n1. Sign in to TikTok in the browser that opens. You can scan the QR code with your phone.\n2. Review Sprid’s requested access and click **Authorize**.\n3. Return to Sprid and check the connected handle.\n\n## How to check it worked\n\nRun `sprid status` and check your TikTok handle. For a publishing check, choose **Only me** for a reviewed post, then find it on your TikTok profile.\n\n## Before publishing\n\nChoose the audience and whether to allow comments each time. Videos also offer Duet and Stitch where your account allows them.\n\nIf a post promotes your business or another brand, complete the commercial-content disclosure. Branded content cannot use **Only me**. Review the music and branded-content terms above the publish button.\n\nThese choices apply to every post shown in a batch. Rescheduling an existing booking keeps that booking’s choices.\n\n## If it fails\n\n- **A public post arrives as private:** use **Send as a draft** and finish publishing in TikTok. Contact [Sprid support](mailto:hello@sprid.studio) about the public-posting restriction.\n- **A publishing limit is reached:** retry later, or use the draft option if available.\n- **“Checking your TikTok account…” stays on screen:** select **Retry**. If it still fails, reconnect.\n- **Publish stays disabled:** complete the audience and disclosure choices and resolve any message shown beside the button.\n\n## Sources\n\n- [Content Posting API](https://developers.tiktok.com/doc/content-posting-api-get-started)\n- [Direct Post reference](https://developers.tiktok.com/doc/content-posting-api-reference-direct-post)\n- [Query creator info](https://developers.tiktok.com/doc/content-posting-api-reference-query-creator-info)\n- [Content Sharing Guidelines](https://developers.tiktok.com/doc/content-sharing-guidelines)\n- [Login Kit for web](https://developers.tiktok.com/doc/login-kit-web)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `posts`, `comments`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source tiktok --json\n```\n\nQueries Sprid’s stored publishes, metric snapshots and inbox for the linked content account. No live platform sync or paid API read. Missing metrics are unmeasured; this does not expose the platform’s entire API. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n",
851
- "revision": "0bc53e6408514913"
844
+ "markdown": "# TikTok\n\n**What Sprid does with this:** Publish approved posts to TikTok, or send drafts to finish in the TikTok app.\n\n## You need\n\nA TikTok account you can sign in to, set to public if you want public posts.\n\n## Then run\n\n```\nsprid connect tiktok --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug.\n\n## Click path (the connect)\n\n1. Sign in to TikTok in the browser that opens, or scan the QR code with your phone.\n2. Review the requested access and click **Authorize**.\n3. Back in Sprid, check the connected handle.\n\n## How to check it worked\n\nRun `sprid status` and check the handle. To test publishing, post a reviewed post as **Only me** and find it on your profile.\n\n## Before publishing\n\nTikTok requires you to choose these on every post, with nothing pre-filled:\n\n- Audience (privacy) and whether to allow comments. Videos also offer Duet and Stitch where your account allows them.\n- The commercial-content disclosure, if the post promotes your business or another brand. Branded content cannot use **Only me**.\n- The music and branded-content terms shown above the publish button.\n\nIn a batch, your choices apply to every post shown. Rescheduling a booking keeps its original choices.\n\n## If it fails\n\n- **A public post arrives as private:** use **Send as a draft** and finish in TikTok, and tell [Sprid support](mailto:hello@sprid.studio) about the public-posting restriction.\n- **Publishing limit reached:** retry later, or send as a draft.\n- **“Checking your TikTok account…” stays on screen:** select **Retry**, then reconnect if it still fails.\n- **Publish stays disabled:** complete the audience and disclosure choices and resolve any message beside the button.\n\n## Investigate with this connection\n\nOperations `posts` and `comments`, read from Sprid’s stored publishes, metric snapshots and inbox (no live platform read; missing metrics are unmeasured, not zero).\n\n```sh\nsprid marketing-review capabilities --app <slug> --source tiktok --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources\n\n- [Content Posting API](https://developers.tiktok.com/doc/content-posting-api-get-started)\n- [Direct Post reference](https://developers.tiktok.com/doc/content-posting-api-reference-direct-post)\n- [Query creator info](https://developers.tiktok.com/doc/content-posting-api-reference-query-creator-info)\n- [Content Sharing Guidelines](https://developers.tiktok.com/doc/content-sharing-guidelines)\n- [Login Kit for web](https://developers.tiktok.com/doc/login-kit-web)\n",
845
+ "revision": "710f233b9a644bb3"
852
846
  },
853
847
  {
854
848
  "id": "youtube",
@@ -861,13 +855,7 @@ export const GUIDES = [
861
855
  "id": "you-need",
862
856
  "title": "You need",
863
857
  "kind": "requirements",
864
- "markdown": "A Google account with a YouTube channel. To create a channel, open [YouTube](https://youtube.com) → your profile picture → **Create a channel**."
865
- },
866
- {
867
- "id": "set-up-additional-channels",
868
- "title": "Set up additional channels",
869
- "kind": "steps",
870
- "markdown": "Open [your channel list](https://www.youtube.com/channel_switcher) and verify the Google account that should own the new channels. Reuse existing channels or choose **Create a channel** for a separate brand, creator, topic or audience. See [Create your social accounts](https://sprid.studio/docs/connect/social-accounts).\n\nIf YouTube requires advanced-feature verification, the owner completes the offered video, ID or channel-history route. Submission can remain pending review. Continue other platforms, then recheck eligibility before retrying. This is separate from authorizing Sprid and was observed when creating additional channels in September 2026."
858
+ "markdown": "A Google account with a YouTube channel. To create one: [YouTube](https://youtube.com) → profile picture → **Create a channel**. For extra channels (another brand, creator or audience) and owner verification, see [Create your social accounts](https://sprid.studio/docs/connect/social-accounts)."
871
859
  },
872
860
  {
873
861
  "id": "then-run",
@@ -879,7 +867,7 @@ export const GUIDES = [
879
867
  "id": "click-path-the-connect",
880
868
  "title": "Click path (the connect)",
881
869
  "kind": "steps",
882
- "markdown": "1. Sign in with your Google account.\n2. Choose the channel you want to upload to, including the Brand Account channel if you use one.\n3. Review Sprid’s requested access and click **Continue**."
870
+ "markdown": "1. Sign in with your Google account.\n2. Choose the channel to upload to, including a Brand Account channel if you use one.\n3. Review the requested access and click **Continue**."
883
871
  },
884
872
  {
885
873
  "id": "how-to-check-it-worked",
@@ -891,23 +879,23 @@ export const GUIDES = [
891
879
  "id": "if-it-fails",
892
880
  "title": "If it fails",
893
881
  "kind": "troubleshooting",
894
- "markdown": "- **Google blocks sign-in or says Sprid is unverified:** contact [Sprid support](mailto:hello@sprid.studio) with the message.\n- **An upload is private although you chose Public:** check its visibility in **YouTube Studio → Content**. If YouTube will not let you change it, contact Sprid support.\n- **Upload limit reached:** wait before retrying. Check the post’s status in Sprid before submitting it again.\n\n- **Channel creation asks for verification:** follow YouTube Studio’s instructions as the primary owner. Keep the channel pending until approval is confirmed; repeated Sprid authorization cannot create it."
882
+ "markdown": "- **Google blocks sign-in or says Sprid is unverified:** send the message to [Sprid support](mailto:hello@sprid.studio).\n- **Upload is private although you chose Public:** check visibility in **YouTube Studio → Content**. If YouTube will not let you change it, contact Sprid support.\n- **Upload limit reached:** wait, and check the post’s status in Sprid before submitting again.\n- **Creating a channel asks for verification:** the primary owner follows YouTube Studio’s instructions (video, ID or channel history). Review can stay pending; reauthorizing Sprid cannot create the channel. Continue other platforms and recheck eligibility later."
883
+ },
884
+ {
885
+ "id": "investigate-with-this-connection",
886
+ "title": "Investigate with this connection",
887
+ "kind": "detail",
888
+ "markdown": "Operations `posts` and `comments`, read from Sprid’s stored publishes, metric snapshots and inbox (no live platform read; missing metrics are unmeasured, not zero).\n\n```sh\nsprid marketing-review capabilities --app <slug> --source youtube --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
895
889
  },
896
890
  {
897
891
  "id": "sources",
898
892
  "title": "Sources",
899
893
  "kind": "sources",
900
894
  "markdown": "- [Feature eligibility and owner verification](https://support.google.com/youtube/answer/9891124?hl=en)\n- [Getting started](https://developers.google.com/youtube/v3/getting-started)\n- [`videos.insert` reference](https://developers.google.com/youtube/v3/docs/videos/insert)\n- [Quota and compliance audits](https://developers.google.com/youtube/v3/guides/quota_and_compliance_audits)\n- [Revision history](https://developers.google.com/youtube/v3/revision_history)"
901
- },
902
- {
903
- "id": "investigate-with-this-connection",
904
- "title": "Investigate with this connection",
905
- "kind": "detail",
906
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `posts`, `comments`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source youtube --json\n```\n\nQueries Sprid’s stored publishes, metric snapshots and inbox for the linked content account. No live platform sync or paid API read. Missing metrics are unmeasured; this does not expose the platform’s entire API. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification."
907
895
  }
908
896
  ],
909
- "markdown": "# YouTube\n\n**What Sprid does with this:** Upload approved videos to your YouTube channel and read their results.\n\n## You need\n\nA Google account with a YouTube channel. To create a channel, open [YouTube](https://youtube.com) → your profile picture → **Create a channel**.\n\n## Set up additional channels\n\nOpen [your channel list](https://www.youtube.com/channel_switcher) and verify the Google account that should own the new channels. Reuse existing channels or choose **Create a channel** for a separate brand, creator, topic or audience. See [Create your social accounts](https://sprid.studio/docs/connect/social-accounts).\n\nIf YouTube requires advanced-feature verification, the owner completes the offered video, ID or channel-history route. Submission can remain pending review. Continue other platforms, then recheck eligibility before retrying. This is separate from authorizing Sprid and was observed when creating additional channels in September 2026.\n\n## Then run\n\n```\nsprid connect youtube --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug.\n\n## Click path (the connect)\n\n1. Sign in with your Google account.\n2. Choose the channel you want to upload to, including the Brand Account channel if you use one.\n3. Review Sprid’s requested access and click **Continue**.\n\n## How to check it worked\n\nRun `sprid status` and check the channel name. Upload a reviewed reel as private, then check **YouTube Studio → Content**.\n\n## If it fails\n\n- **Google blocks sign-in or says Sprid is unverified:** contact [Sprid support](mailto:hello@sprid.studio) with the message.\n- **An upload is private although you chose Public:** check its visibility in **YouTube Studio → Content**. If YouTube will not let you change it, contact Sprid support.\n- **Upload limit reached:** wait before retrying. Check the post’s status in Sprid before submitting it again.\n\n- **Channel creation asks for verification:** follow YouTube Studio’s instructions as the primary owner. Keep the channel pending until approval is confirmed; repeated Sprid authorization cannot create it.\n\n## Sources\n\n- [Feature eligibility and owner verification](https://support.google.com/youtube/answer/9891124?hl=en)\n- [Getting started](https://developers.google.com/youtube/v3/getting-started)\n- [`videos.insert` reference](https://developers.google.com/youtube/v3/docs/videos/insert)\n- [Quota and compliance audits](https://developers.google.com/youtube/v3/guides/quota_and_compliance_audits)\n- [Revision history](https://developers.google.com/youtube/v3/revision_history)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `posts`, `comments`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source youtube --json\n```\n\nQueries Sprid’s stored publishes, metric snapshots and inbox for the linked content account. No live platform sync or paid API read. Missing metrics are unmeasured; this does not expose the platform’s entire API. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n",
910
- "revision": "f9cab3463a986a59"
897
+ "markdown": "# YouTube\n\n**What Sprid does with this:** Upload approved videos to your YouTube channel and read their results.\n\n## You need\n\nA Google account with a YouTube channel. To create one: [YouTube](https://youtube.com) → profile picture → **Create a channel**. For extra channels (another brand, creator or audience) and owner verification, see [Create your social accounts](https://sprid.studio/docs/connect/social-accounts).\n\n## Then run\n\n```\nsprid connect youtube --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug.\n\n## Click path (the connect)\n\n1. Sign in with your Google account.\n2. Choose the channel to upload to, including a Brand Account channel if you use one.\n3. Review the requested access and click **Continue**.\n\n## How to check it worked\n\nRun `sprid status` and check the channel name. Upload a reviewed reel as private, then check **YouTube Studio → Content**.\n\n## If it fails\n\n- **Google blocks sign-in or says Sprid is unverified:** send the message to [Sprid support](mailto:hello@sprid.studio).\n- **Upload is private although you chose Public:** check visibility in **YouTube Studio → Content**. If YouTube will not let you change it, contact Sprid support.\n- **Upload limit reached:** wait, and check the post’s status in Sprid before submitting again.\n- **Creating a channel asks for verification:** the primary owner follows YouTube Studio’s instructions (video, ID or channel history). Review can stay pending; reauthorizing Sprid cannot create the channel. Continue other platforms and recheck eligibility later.\n\n## Investigate with this connection\n\nOperations `posts` and `comments`, read from Sprid’s stored publishes, metric snapshots and inbox (no live platform read; missing metrics are unmeasured, not zero).\n\n```sh\nsprid marketing-review capabilities --app <slug> --source youtube --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources\n\n- [Feature eligibility and owner verification](https://support.google.com/youtube/answer/9891124?hl=en)\n- [Getting started](https://developers.google.com/youtube/v3/getting-started)\n- [`videos.insert` reference](https://developers.google.com/youtube/v3/docs/videos/insert)\n- [Quota and compliance audits](https://developers.google.com/youtube/v3/guides/quota_and_compliance_audits)\n- [Revision history](https://developers.google.com/youtube/v3/revision_history)\n",
898
+ "revision": "5c7cb25150e1d261"
911
899
  },
912
900
  {
913
901
  "id": "linkedin",
@@ -920,7 +908,7 @@ export const GUIDES = [
920
908
  "id": "you-need",
921
909
  "title": "You need",
922
910
  "kind": "requirements",
923
- "markdown": "A LinkedIn account. Check the connected destination before publishing: Company Page availability depends on Sprid’s LinkedIn approval. A Page also requires **Super admin** or **Content admin** access."
911
+ "markdown": "A LinkedIn account. Publishing to a Company Page needs **Super admin** or **Content admin** access on the Page."
924
912
  },
925
913
  {
926
914
  "id": "then-run",
@@ -932,35 +920,35 @@ export const GUIDES = [
932
920
  "id": "click-path-the-connect",
933
921
  "title": "Click path (the connect)",
934
922
  "kind": "steps",
935
- "markdown": "1. Sign in to LinkedIn in the browser that opens.\n2. Review the access Sprid requests and click **Allow**.\n3. Return to Sprid and check the connected name."
923
+ "markdown": "1. Sign in to LinkedIn in the browser that opens.\n2. Review the requested access and click **Allow**.\n3. Back in Sprid, check the connected name."
936
924
  },
937
925
  {
938
926
  "id": "how-to-check-it-worked",
939
927
  "title": "How to check it worked",
940
928
  "kind": "verify",
941
- "markdown": "Run `sprid status` and confirm the destination. After publishing a reviewed carousel, open LinkedIn and check the document post."
929
+ "markdown": "Run `sprid status` and confirm the destination. After publishing a reviewed carousel, check the document post on LinkedIn."
942
930
  },
943
931
  {
944
932
  "id": "if-it-fails",
945
933
  "title": "If it fails",
946
934
  "kind": "troubleshooting",
947
- "markdown": "- **Company Page missing:** ask its Super admin to check your role under **Page → Settings → Manage admins**. Contact [Sprid support](mailto:hello@sprid.studio) if Page publishing is unavailable.\n- **Access denied:** reconnect, then check you still have permission to publish to the destination.\n- **Connection expiring or expired:** run the connect command again."
935
+ "markdown": "- **Company Page missing:** ask its Super admin to check your role under **Page → Settings → Manage admins**. If Page publishing is unavailable, contact [Sprid support](mailto:hello@sprid.studio).\n- **Access denied:** reconnect and check you can still publish to the destination.\n- **Connection expiring or expired:** run the connect command again."
936
+ },
937
+ {
938
+ "id": "investigate-with-this-connection",
939
+ "title": "Investigate with this connection",
940
+ "kind": "detail",
941
+ "markdown": "Operations `posts` and `comments`, read from Sprid’s stored publishes, metric snapshots and inbox (no live platform read; missing metrics are unmeasured, not zero).\n\n```sh\nsprid marketing-review capabilities --app <slug> --source linkedin --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
948
942
  },
949
943
  {
950
944
  "id": "sources",
951
945
  "title": "Sources",
952
946
  "kind": "sources",
953
947
  "markdown": "- [Share on LinkedIn](https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/share-on-linkedin)\n- [Posts API permissions](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api)\n- [Documents API](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/documents-api)"
954
- },
955
- {
956
- "id": "investigate-with-this-connection",
957
- "title": "Investigate with this connection",
958
- "kind": "detail",
959
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `posts`, `comments`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source linkedin --json\n```\n\nQueries Sprid’s stored publishes, metric snapshots and inbox for the linked content account. No live platform sync or paid API read. Missing metrics are unmeasured; this does not expose the platform’s entire API. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification."
960
948
  }
961
949
  ],
962
- "markdown": "# LinkedIn\n\n**What Sprid does with this:** Publish approved carousels to LinkedIn as swipeable document posts.\n\n## You need\n\nA LinkedIn account. Check the connected destination before publishing: Company Page availability depends on Sprid’s LinkedIn approval. A Page also requires **Super admin** or **Content admin** access.\n\n## Then run\n\n```\nsprid connect linkedin --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug.\n\n## Click path (the connect)\n\n1. Sign in to LinkedIn in the browser that opens.\n2. Review the access Sprid requests and click **Allow**.\n3. Return to Sprid and check the connected name.\n\n## How to check it worked\n\nRun `sprid status` and confirm the destination. After publishing a reviewed carousel, open LinkedIn and check the document post.\n\n## If it fails\n\n- **Company Page missing:** ask its Super admin to check your role under **Page → Settings → Manage admins**. Contact [Sprid support](mailto:hello@sprid.studio) if Page publishing is unavailable.\n- **Access denied:** reconnect, then check you still have permission to publish to the destination.\n- **Connection expiring or expired:** run the connect command again.\n\n## Sources\n\n- [Share on LinkedIn](https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/share-on-linkedin)\n- [Posts API permissions](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api)\n- [Documents API](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/documents-api)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `posts`, `comments`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source linkedin --json\n```\n\nQueries Sprid’s stored publishes, metric snapshots and inbox for the linked content account. No live platform sync or paid API read. Missing metrics are unmeasured; this does not expose the platform’s entire API. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n",
963
- "revision": "983ed80b263badc6"
950
+ "markdown": "# LinkedIn\n\n**What Sprid does with this:** Publish approved carousels to LinkedIn as swipeable document posts.\n\n## You need\n\nA LinkedIn account. Publishing to a Company Page needs **Super admin** or **Content admin** access on the Page.\n\n## Then run\n\n```\nsprid connect linkedin --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug.\n\n## Click path (the connect)\n\n1. Sign in to LinkedIn in the browser that opens.\n2. Review the requested access and click **Allow**.\n3. Back in Sprid, check the connected name.\n\n## How to check it worked\n\nRun `sprid status` and confirm the destination. After publishing a reviewed carousel, check the document post on LinkedIn.\n\n## If it fails\n\n- **Company Page missing:** ask its Super admin to check your role under **Page → Settings → Manage admins**. If Page publishing is unavailable, contact [Sprid support](mailto:hello@sprid.studio).\n- **Access denied:** reconnect and check you can still publish to the destination.\n- **Connection expiring or expired:** run the connect command again.\n\n## Investigate with this connection\n\nOperations `posts` and `comments`, read from Sprid’s stored publishes, metric snapshots and inbox (no live platform read; missing metrics are unmeasured, not zero).\n\n```sh\nsprid marketing-review capabilities --app <slug> --source linkedin --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources\n\n- [Share on LinkedIn](https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/share-on-linkedin)\n- [Posts API permissions](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api)\n- [Documents API](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/documents-api)\n",
951
+ "revision": "40239bfd7b93d752"
964
952
  },
965
953
  {
966
954
  "id": "facebook",
@@ -973,7 +961,7 @@ export const GUIDES = [
973
961
  "id": "you-need",
974
962
  "title": "You need",
975
963
  "kind": "requirements",
976
- "markdown": "A Facebook **Page** you can publish to. Check **Professional dashboard → Page access** for **Facebook access** or **task access** that includes **Content**. Personal profiles cannot be connected for publishing. Instagram does not need to be linked.\n\nNeed to create or organize Pages first? Follow [Create your social accounts](https://sprid.studio/docs/connect/social-accounts). Portfolio ownership is a separate check from a successful Sprid connection."
964
+ "markdown": "A Facebook **Page** you can publish to: **Professional dashboard → Page access** should show **Facebook access**, or **task access** that includes **Content**. Personal profiles cannot be connected, and Instagram does not need to be linked. To create a Page or add it to a business portfolio, see [Create your social accounts](https://sprid.studio/docs/connect/social-accounts)."
977
965
  },
978
966
  {
979
967
  "id": "then-run",
@@ -985,35 +973,35 @@ export const GUIDES = [
985
973
  "id": "click-path-the-connect",
986
974
  "title": "Click path (the connect)",
987
975
  "kind": "steps",
988
- "markdown": "1. Sign in to Facebook with the profile that manages your Page.\n2. Select the Page you want to connect and leave the requested permissions enabled.\n3. Click **Continue / Done** to return to Sprid.\n4. If Sprid lists several Pages, choose one by rerunning the command with `--page <id>`, using the id shown in the message."
976
+ "markdown": "1. Sign in with the Facebook profile that manages the Page.\n2. Select the Page and leave the requested permissions enabled.\n3. Click **Continue / Done** to return to Sprid.\n4. If Sprid lists several Pages, rerun with `--page <id>` using the id in the message. The profile’s name is not the Page destination."
989
977
  },
990
978
  {
991
979
  "id": "how-to-check-it-worked",
992
980
  "title": "How to check it worked",
993
981
  "kind": "verify",
994
- "markdown": "Run `sprid status` and check the connected Page’s name. When you publish a reviewed post, check that it appears on that Page."
982
+ "markdown": "Run `sprid status` and check the Page name. After publishing a reviewed post, check it on that Page."
995
983
  },
996
984
  {
997
985
  "id": "if-it-fails",
998
986
  "title": "If it fails",
999
987
  "kind": "troubleshooting",
1000
- "markdown": "- **No Pages found:** check your Page access, reconnect and select the Page in Facebook’s chooser.\n- **Permission missing:** reconnect and approve all requested permissions.\n- **Page already connected:** check that you selected the intended Page. Each Page connects to one Sprid account; disconnect the existing connection only if you intentionally want to move that Page.\n- **“Invalid Scopes”, app unavailable or “URL blocked”:** contact [Sprid support](mailto:hello@sprid.studio). These need a fix from Sprid."
988
+ "markdown": "- **No Pages found:** check your Page access, reconnect and select the Page in Facebook’s chooser.\n- **Permission missing:** reconnect and approve all requested permissions.\n- **Page already connected:** check you picked the intended Page. A Page connects to one Sprid account; disconnect the old one only if you mean to move it.\n- **“Invalid Scopes”, app unavailable or “URL blocked”:** contact [Sprid support](mailto:hello@sprid.studio). Only Sprid can fix these."
989
+ },
990
+ {
991
+ "id": "investigate-with-this-connection",
992
+ "title": "Investigate with this connection",
993
+ "kind": "detail",
994
+ "markdown": "Operations `posts` and `comments`, read from Sprid’s stored publishes, metric snapshots and inbox (no live platform read; missing metrics are unmeasured, not zero).\n\n```sh\nsprid marketing-review capabilities --app <slug> --source facebook --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
1001
995
  },
1002
996
  {
1003
997
  "id": "sources",
1004
998
  "title": "Sources",
1005
999
  "kind": "sources",
1006
1000
  "markdown": "- [Pages API getting started](https://developers.facebook.com/docs/pages-api/getting-started)\n- [Permission descriptions and review requirements](https://developers.facebook.com/docs/permissions)\n- [App modes](https://developers.facebook.com/docs/development/build-and-test/app-modes)\n- [Facebook access vs task access on the new Pages experience](https://www.facebook.com/business/help/582754542592549)"
1007
- },
1008
- {
1009
- "id": "investigate-with-this-connection",
1010
- "title": "Investigate with this connection",
1011
- "kind": "detail",
1012
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `posts`, `comments`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source facebook --json\n```\n\nQueries Sprid’s stored publishes, metric snapshots and inbox for the linked content account. No live platform sync or paid API read. Missing metrics are unmeasured; this does not expose the platform’s entire API. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification."
1013
1001
  }
1014
1002
  ],
1015
- "markdown": "# Facebook Page\n\n**What Sprid does with this:** Publish approved posts to your Facebook Page and see their results.\n\n## You need\n\nA Facebook **Page** you can publish to. Check **Professional dashboard → Page access** for **Facebook access** or **task access** that includes **Content**. Personal profiles cannot be connected for publishing. Instagram does not need to be linked.\n\nNeed to create or organize Pages first? Follow [Create your social accounts](https://sprid.studio/docs/connect/social-accounts). Portfolio ownership is a separate check from a successful Sprid connection.\n\n## Then run\n\n```\nsprid connect facebook --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug.\n\n## Click path (the connect)\n\n1. Sign in to Facebook with the profile that manages your Page.\n2. Select the Page you want to connect and leave the requested permissions enabled.\n3. Click **Continue / Done** to return to Sprid.\n4. If Sprid lists several Pages, choose one by rerunning the command with `--page <id>`, using the id shown in the message.\n\n## How to check it worked\n\nRun `sprid status` and check the connected Page’s name. When you publish a reviewed post, check that it appears on that Page.\n\n## If it fails\n\n- **No Pages found:** check your Page access, reconnect and select the Page in Facebook’s chooser.\n- **Permission missing:** reconnect and approve all requested permissions.\n- **Page already connected:** check that you selected the intended Page. Each Page connects to one Sprid account; disconnect the existing connection only if you intentionally want to move that Page.\n- **“Invalid Scopes”, app unavailable or “URL blocked”:** contact [Sprid support](mailto:hello@sprid.studio). These need a fix from Sprid.\n\n## Sources\n\n- [Pages API getting started](https://developers.facebook.com/docs/pages-api/getting-started)\n- [Permission descriptions and review requirements](https://developers.facebook.com/docs/permissions)\n- [App modes](https://developers.facebook.com/docs/development/build-and-test/app-modes)\n- [Facebook access vs task access on the new Pages experience](https://www.facebook.com/business/help/582754542592549)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `posts`, `comments`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source facebook --json\n```\n\nQueries Sprid’s stored publishes, metric snapshots and inbox for the linked content account. No live platform sync or paid API read. Missing metrics are unmeasured; this does not expose the platform’s entire API. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n",
1016
- "revision": "0e61f4caa24da0e1"
1003
+ "markdown": "# Facebook Page\n\n**What Sprid does with this:** Publish approved posts to your Facebook Page and see their results.\n\n## You need\n\nA Facebook **Page** you can publish to: **Professional dashboard → Page access** should show **Facebook access**, or **task access** that includes **Content**. Personal profiles cannot be connected, and Instagram does not need to be linked. To create a Page or add it to a business portfolio, see [Create your social accounts](https://sprid.studio/docs/connect/social-accounts).\n\n## Then run\n\n```\nsprid connect facebook --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug.\n\n## Click path (the connect)\n\n1. Sign in with the Facebook profile that manages the Page.\n2. Select the Page and leave the requested permissions enabled.\n3. Click **Continue / Done** to return to Sprid.\n4. If Sprid lists several Pages, rerun with `--page <id>` using the id in the message. The profile’s name is not the Page destination.\n\n## How to check it worked\n\nRun `sprid status` and check the Page name. After publishing a reviewed post, check it on that Page.\n\n## If it fails\n\n- **No Pages found:** check your Page access, reconnect and select the Page in Facebook’s chooser.\n- **Permission missing:** reconnect and approve all requested permissions.\n- **Page already connected:** check you picked the intended Page. A Page connects to one Sprid account; disconnect the old one only if you mean to move it.\n- **“Invalid Scopes”, app unavailable or “URL blocked”:** contact [Sprid support](mailto:hello@sprid.studio). Only Sprid can fix these.\n\n## Investigate with this connection\n\nOperations `posts` and `comments`, read from Sprid’s stored publishes, metric snapshots and inbox (no live platform read; missing metrics are unmeasured, not zero).\n\n```sh\nsprid marketing-review capabilities --app <slug> --source facebook --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources\n\n- [Pages API getting started](https://developers.facebook.com/docs/pages-api/getting-started)\n- [Permission descriptions and review requirements](https://developers.facebook.com/docs/permissions)\n- [App modes](https://developers.facebook.com/docs/development/build-and-test/app-modes)\n- [Facebook access vs task access on the new Pages experience](https://www.facebook.com/business/help/582754542592549)\n",
1004
+ "revision": "414500d53feab293"
1017
1005
  },
1018
1006
  {
1019
1007
  "id": "x",
@@ -1038,7 +1026,7 @@ export const GUIDES = [
1038
1026
  "id": "click-path-the-connect",
1039
1027
  "title": "Click path (the connect)",
1040
1028
  "kind": "steps",
1041
- "markdown": "1. Sign in to the X account you want to use.\n2. Review Sprid’s requested access and click **Authorize app**.\n3. Return to Sprid and check the connected handle."
1029
+ "markdown": "1. Sign in to the X account you want to use.\n2. Review the requested access and click **Authorize app**.\n3. Back in Sprid, check the connected handle."
1042
1030
  },
1043
1031
  {
1044
1032
  "id": "how-to-check-it-worked",
@@ -1050,29 +1038,29 @@ export const GUIDES = [
1050
1038
  "id": "the-caption-is-its-own-field",
1051
1039
  "title": "The caption is its own field",
1052
1040
  "kind": "detail",
1053
- "markdown": "Keep the **X caption** within **280 characters**. If the box is empty, Sprid uses your shared caption. An overlong caption must be shortened before publishing.\n\nCarousels with more than four images publish as a thread, with the caption on the first post."
1041
+ "markdown": "The **X caption** has a **280-character** limit. Left empty, it uses your shared caption, which must then fit too. Carousels over four images publish as a thread, with the caption on the first post."
1054
1042
  },
1055
1043
  {
1056
1044
  "id": "if-it-fails",
1057
1045
  "title": "If it fails",
1058
1046
  "kind": "troubleshooting",
1059
- "markdown": "- **Connection expired or refresh failed:** run the connect command again.\n- **Access denied after reconnecting:** contact [Sprid support](mailto:hello@sprid.studio) with the error message.\n- **Publishing limit reached:** check the post’s status and retry when the limit resets."
1047
+ "markdown": "- **Connection expired or refresh failed:** run the connect command again.\n- **Access denied after reconnecting:** send the error to [Sprid support](mailto:hello@sprid.studio).\n- **Publishing limit reached:** check the post’s status and retry when the limit resets."
1048
+ },
1049
+ {
1050
+ "id": "investigate-with-this-connection",
1051
+ "title": "Investigate with this connection",
1052
+ "kind": "detail",
1053
+ "markdown": "Operations `posts` and `comments`, read from Sprid’s stored publishes, metric snapshots and inbox (no live platform read; missing metrics are unmeasured, not zero).\n\n```sh\nsprid marketing-review capabilities --app <slug> --source x --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries)."
1060
1054
  },
1061
1055
  {
1062
1056
  "id": "sources",
1063
1057
  "title": "Sources",
1064
1058
  "kind": "sources",
1065
1059
  "markdown": "- [OAuth 2.0 authorization code with PKCE](https://docs.x.com/resources/fundamentals/authentication/oauth-2-0/authorization-code)\n- [Create a Post](https://docs.x.com/x-api/posts/create-post)\n- [Chunked media upload](https://docs.x.com/x-api/media/quickstart/media-upload-chunked)"
1066
- },
1067
- {
1068
- "id": "investigate-with-this-connection",
1069
- "title": "Investigate with this connection",
1070
- "kind": "detail",
1071
- "markdown": "Your agent can use `list_marketing_queries` and `query_marketing_source` for `posts`, `comments`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source x --json\n```\n\nQueries Sprid’s stored publishes, metric snapshots and inbox for the linked content account. No live platform sync or paid API read. Missing metrics are unmeasured; this does not expose the platform’s entire API. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification."
1072
1060
  }
1073
1061
  ],
1074
- "markdown": "# X\n\n**What Sprid does with this:** Publish approved posts to X and see how they perform.\n\n## You need\n\nAn X account you can sign in to. Sprid handles the developer connection.\n\n## Then run\n\n```\nsprid connect x --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug.\n\n## Click path (the connect)\n\n1. Sign in to the X account you want to use.\n2. Review Sprid’s requested access and click **Authorize app**.\n3. Return to Sprid and check the connected handle.\n\n## How to check it worked\n\nRun `sprid status` and confirm the handle. After publishing a reviewed post, check it on X.\n\n## The caption is its own field\n\nKeep the **X caption** within **280 characters**. If the box is empty, Sprid uses your shared caption. An overlong caption must be shortened before publishing.\n\nCarousels with more than four images publish as a thread, with the caption on the first post.\n\n## If it fails\n\n- **Connection expired or refresh failed:** run the connect command again.\n- **Access denied after reconnecting:** contact [Sprid support](mailto:hello@sprid.studio) with the error message.\n- **Publishing limit reached:** check the post’s status and retry when the limit resets.\n\n## Sources\n\n- [OAuth 2.0 authorization code with PKCE](https://docs.x.com/resources/fundamentals/authentication/oauth-2-0/authorization-code)\n- [Create a Post](https://docs.x.com/x-api/posts/create-post)\n- [Chunked media upload](https://docs.x.com/x-api/media/quickstart/media-upload-chunked)\n\n## Investigate with this connection\n\nYour agent can use `list_marketing_queries` and `query_marketing_source` for `posts`, `comments`. Discover the exact parameters and required setup with:\n\n```sh\nsprid marketing-review capabilities --app <slug> --source x --json\n```\n\nQueries Sprid’s stored publishes, metric snapshots and inbox for the linked content account. No live platform sync or paid API read. Missing metrics are unmeasured; this does not expose the platform’s entire API. See [connected queries](https://sprid.studio/docs/queries) for the shared workflow. Extra operations may need additional read permissions; a stored key alone is not live verification.\n",
1075
- "revision": "d7675df2049b1d47"
1062
+ "markdown": "# X\n\n**What Sprid does with this:** Publish approved posts to X and see how they perform.\n\n## You need\n\nAn X account you can sign in to. Sprid handles the developer connection.\n\n## Then run\n\n```\nsprid connect x --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug.\n\n## Click path (the connect)\n\n1. Sign in to the X account you want to use.\n2. Review the requested access and click **Authorize app**.\n3. Back in Sprid, check the connected handle.\n\n## How to check it worked\n\nRun `sprid status` and confirm the handle. After publishing a reviewed post, check it on X.\n\n## The caption is its own field\n\nThe **X caption** has a **280-character** limit. Left empty, it uses your shared caption, which must then fit too. Carousels over four images publish as a thread, with the caption on the first post.\n\n## If it fails\n\n- **Connection expired or refresh failed:** run the connect command again.\n- **Access denied after reconnecting:** send the error to [Sprid support](mailto:hello@sprid.studio).\n- **Publishing limit reached:** check the post’s status and retry when the limit resets.\n\n## Investigate with this connection\n\nOperations `posts` and `comments`, read from Sprid’s stored publishes, metric snapshots and inbox (no live platform read; missing metrics are unmeasured, not zero).\n\n```sh\nsprid marketing-review capabilities --app <slug> --source x --json\n```\n\nSee [connected queries](https://sprid.studio/docs/queries).\n\n## Sources\n\n- [OAuth 2.0 authorization code with PKCE](https://docs.x.com/resources/fundamentals/authentication/oauth-2-0/authorization-code)\n- [Create a Post](https://docs.x.com/x-api/posts/create-post)\n- [Chunked media upload](https://docs.x.com/x-api/media/quickstart/media-upload-chunked)\n",
1063
+ "revision": "3f5be01bb8544888"
1076
1064
  },
1077
1065
  {
1078
1066
  "id": "pinterest",
@@ -1085,43 +1073,43 @@ export const GUIDES = [
1085
1073
  "id": "you-need",
1086
1074
  "title": "You need",
1087
1075
  "kind": "requirements",
1088
- "markdown": "- A Pinterest account you can sign in to. A free Pinterest business account is recommended because Pinterest Analytics requires one.\n- Access to the Sprid account that will own this channel.\n- A board topic in mind. A specific board gives Pinterest and people useful context about the Pin; Sprid can create it during publishing.\n\nYou do **not** need to create a Pinterest developer app, request API access or copy a token. Sprid owns the developer integration; you only authorize your own Pinterest account."
1076
+ "markdown": "- A Pinterest account. A free business account is recommended, because Pinterest Analytics requires one.\n- Access to the Sprid account that will own this channel.\n- A board topic in mind. Sprid can create the board during publishing.\n\nYou do **not** create a Pinterest developer app, request API access or copy a token. You only authorize your own Pinterest account."
1089
1077
  },
1090
1078
  {
1091
1079
  "id": "then-run",
1092
1080
  "title": "Then run",
1093
1081
  "kind": "command",
1094
- "markdown": "```sh\nsprid connect pinterest --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug."
1082
+ "markdown": "```sh\nsprid connect pinterest --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug, or use **Settings → Accounts → [account] → Pinterest → Continue with Pinterest** in Sprid."
1095
1083
  },
1096
1084
  {
1097
1085
  "id": "click-path-the-connect",
1098
1086
  "title": "Click path (the connect)",
1099
1087
  "kind": "steps",
1100
- "markdown": "1. In Sprid, open **Settings → Accounts → [account] → Pinterest**.\n2. Select **Continue with Pinterest**.\n3. Check the Pinterest identity in the browser that opens. Sign out first if it is the wrong account.\n4. Review the access Sprid requests, then authorize Sprid.\n5. Return to Sprid and confirm the connected Pinterest name.\n6. Open a post, select **Publish → Pinterest**, then confirm the intended destination in the **Board** picker. If none fits, select **Create board**, name it and choose whether it is public or secret. Sprid selects the new board automatically.\n\nConnecting one Pinterest account does not connect another account signed in under the same browser. Repeat the command for each Sprid account and check the destination every time."
1088
+ "markdown": "1. Check the Pinterest identity in the browser that opens. Sign out first if it is the wrong account.\n2. Review the requested access and authorize Sprid.\n3. Back in Sprid, confirm the connected Pinterest name.\n4. Open a post, select **Publish → Pinterest** and pick the destination in the **Board** picker. If none fits, select **Create board**, name it and choose public or secret; Sprid selects it automatically.\n\nEach Sprid account needs its own connect, even when several Pinterest accounts are signed in to the same browser. Check the identity every time."
1101
1089
  },
1102
1090
  {
1103
1091
  "id": "check-the-connection",
1104
1092
  "title": "Check the connection",
1105
1093
  "kind": "verify",
1106
- "markdown": "Run `sprid status` and confirm that Pinterest shows the intended account. Open a draft Pin, select **Publish → Pinterest**, and confirm that its intended board appears before approving any schedule.\n\nThe first public Pin is the final check: after it publishes, open Pinterest while signed out or from another account and confirm the Pin, board, visual, title and destination. A successful API response alone does not prove public visibility."
1094
+ "markdown": "Run `sprid status` and confirm Pinterest shows the intended account. Open a draft Pin, select **Publish → Pinterest** and confirm the intended board appears before approving any schedule.\n\nAfter the first Pin publishes, open Pinterest signed out or from another account and check the Pin, board, visual, title and destination. A successful API response does not prove public visibility."
1107
1095
  },
1108
1096
  {
1109
1097
  "id": "trial-and-standard-access",
1110
1098
  "title": "Trial and Standard access",
1111
1099
  "kind": "detail",
1112
- "markdown": "Pinterest gives developer integrations Trial access for testing and Standard access for production. Sprid lets you connect an account and browse its boards while its app has Trial access, but it refuses both publishing and scheduling. Publishing becomes available only after **Sprid’s** Pinterest app has Standard access.\n\nThis is Sprid’s operational approval, not an application each customer completes. If Sprid reports that Pinterest publishing requires Standard access, keep preparing and reviewing drafts, but do not treat them as booked. Reconnecting your account will not change the access tier. Follow the status message or contact Sprid support."
1100
+ "markdown": "Publishing and scheduling need **Sprid’s** Pinterest app to have Standard access. With Trial access you can connect and browse boards, but Sprid refuses to publish or schedule. This is Sprid’s approval, not something you apply for, and reconnecting does not change it. Keep preparing drafts, but do not treat them as booked."
1113
1101
  },
1114
1102
  {
1115
1103
  "id": "prepare-the-account",
1116
1104
  "title": "Prepare the account",
1117
1105
  "kind": "detail",
1118
- "markdown": "Use a business account when you need analytics. Create a small set of specific boards around topics people actually search for, such as “Small apartment viewing checklist” instead of “Inspiration”. Board names, descriptions and the Pins saved to them help establish context.\n\nClaim an owned website in Pinterest when possible. Claiming associates Pins from that site with the profile and expands website analytics. One website can be claimed by only one Pinterest account, so decide which market or brand account owns it before connecting several accounts in Sprid.\n\nRead [Pinterest content and publishing](https://sprid.studio/docs/pinterest) before preparing the first batch."
1106
+ "markdown": "Create a few specific boards around topics people search for, such as “Small apartment viewing checklist” rather than “Inspiration”. Board names, descriptions and saved Pins give Pinterest context.\n\nClaim your website in Pinterest where possible: it links Pins from that site to the profile and expands website analytics. A website can be claimed by only one Pinterest account, so decide which market or brand owns it before connecting several.\n\nRead [Pinterest content and publishing](https://sprid.studio/docs/pinterest) before preparing the first batch."
1119
1107
  },
1120
1108
  {
1121
1109
  "id": "if-it-fails",
1122
1110
  "title": "If it fails",
1123
1111
  "kind": "troubleshooting",
1124
- "markdown": "- **“Pinterest is unavailable until API credentials and an access tier are configured”:** this Sprid deployment is not ready for Pinterest. The customer does not create a developer app or provide credentials; follow Sprid’s availability status or contact support.\n- **Wrong Pinterest account connected:** disconnect it in Sprid, sign out of Pinterest in that browser, then run the command again and check the identity before authorizing.\n- **“No Pinterest boards yet”:** create one from the board picker. If Sprid asks for board permission, reconnect Pinterest once; connections made before board creation support do not carry the required write scope.\n- **Existing boards are missing:** reconnect once, then contact [Sprid support](mailto:hello@sprid.studio) with the account name and error message if they still do not appear.\n- **Access denied:** confirm that the Pinterest account is active and that you completed the consent screen. Retry once; if Pinterest still refuses access, contact Sprid support.\n- **Publishing or scheduling requires Standard access:** the account can remain connected and its boards can still be selected during setup. Sprid will not create Trial-access Pins. Only Sprid can resolve its developer access tier.\n- **Analytics unavailable:** use a Pinterest business account and check that the Pin is public. Treat unavailable data as unavailable, never as zero."
1112
+ "markdown": "- **“Pinterest is unavailable until API credentials and an access tier are configured”:** this Sprid deployment is not ready for Pinterest. Follow Sprid’s status or contact support; you do not supply credentials.\n- **Wrong account connected:** disconnect it in Sprid, sign out of Pinterest in that browser, then reconnect and check the identity before authorizing.\n- **“No Pinterest boards yet”:** create one from the board picker. If Sprid asks for board permission, reconnect once; older connections lack the board write scope.\n- **Existing boards missing:** reconnect once, then contact [Sprid support](mailto:hello@sprid.studio) with the account name and error.\n- **Access denied:** confirm the Pinterest account is active and you finished the consent screen. Retry once, then contact Sprid support.\n- **Publishing or scheduling requires Standard access:** see Trial and Standard access above. Boards stay selectable; only Sprid can change the tier.\n- **Analytics unavailable:** use a business account and check the Pin is public. Unavailable data is unavailable, not zero."
1125
1113
  },
1126
1114
  {
1127
1115
  "id": "sources",
@@ -1130,49 +1118,73 @@ export const GUIDES = [
1130
1118
  "markdown": "- [Pinterest developer access tiers](https://developer.pinterest.com/docs/key-concepts/access-tiers/)\n- [Pinterest developer guidelines](https://policy.pinterest.com/en/developer-guidelines)\n- [Claim your website](https://help.pinterest.com/en/business/article/claim-your-website)\n- [Pinterest Analytics](https://help.pinterest.com/en/business/article/pinterest-analytics)"
1131
1119
  }
1132
1120
  ],
1133
- "markdown": "# Pinterest\n\n**What Sprid does with this:** Publish the Pins you select to the right boards, keep their destination links intact and read their results when analytics access is available.\n\n## You need\n\n- A Pinterest account you can sign in to. A free Pinterest business account is recommended because Pinterest Analytics requires one.\n- Access to the Sprid account that will own this channel.\n- A board topic in mind. A specific board gives Pinterest and people useful context about the Pin; Sprid can create it during publishing.\n\nYou do **not** need to create a Pinterest developer app, request API access or copy a token. Sprid owns the developer integration; you only authorize your own Pinterest account.\n\n## Then run\n\n```sh\nsprid connect pinterest --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug.\n\n## Click path (the connect)\n\n1. In Sprid, open **Settings → Accounts → [account] → Pinterest**.\n2. Select **Continue with Pinterest**.\n3. Check the Pinterest identity in the browser that opens. Sign out first if it is the wrong account.\n4. Review the access Sprid requests, then authorize Sprid.\n5. Return to Sprid and confirm the connected Pinterest name.\n6. Open a post, select **Publish → Pinterest**, then confirm the intended destination in the **Board** picker. If none fits, select **Create board**, name it and choose whether it is public or secret. Sprid selects the new board automatically.\n\nConnecting one Pinterest account does not connect another account signed in under the same browser. Repeat the command for each Sprid account and check the destination every time.\n\n## Check the connection\n\nRun `sprid status` and confirm that Pinterest shows the intended account. Open a draft Pin, select **Publish → Pinterest**, and confirm that its intended board appears before approving any schedule.\n\nThe first public Pin is the final check: after it publishes, open Pinterest while signed out or from another account and confirm the Pin, board, visual, title and destination. A successful API response alone does not prove public visibility.\n\n## Trial and Standard access\n\nPinterest gives developer integrations Trial access for testing and Standard access for production. Sprid lets you connect an account and browse its boards while its app has Trial access, but it refuses both publishing and scheduling. Publishing becomes available only after **Sprid’s** Pinterest app has Standard access.\n\nThis is Sprid’s operational approval, not an application each customer completes. If Sprid reports that Pinterest publishing requires Standard access, keep preparing and reviewing drafts, but do not treat them as booked. Reconnecting your account will not change the access tier. Follow the status message or contact Sprid support.\n\n## Prepare the account\n\nUse a business account when you need analytics. Create a small set of specific boards around topics people actually search for, such as “Small apartment viewing checklist” instead of “Inspiration”. Board names, descriptions and the Pins saved to them help establish context.\n\nClaim an owned website in Pinterest when possible. Claiming associates Pins from that site with the profile and expands website analytics. One website can be claimed by only one Pinterest account, so decide which market or brand account owns it before connecting several accounts in Sprid.\n\nRead [Pinterest content and publishing](https://sprid.studio/docs/pinterest) before preparing the first batch.\n\n## If it fails\n\n- **“Pinterest is unavailable until API credentials and an access tier are configured”:** this Sprid deployment is not ready for Pinterest. The customer does not create a developer app or provide credentials; follow Sprid’s availability status or contact support.\n- **Wrong Pinterest account connected:** disconnect it in Sprid, sign out of Pinterest in that browser, then run the command again and check the identity before authorizing.\n- **“No Pinterest boards yet”:** create one from the board picker. If Sprid asks for board permission, reconnect Pinterest once; connections made before board creation support do not carry the required write scope.\n- **Existing boards are missing:** reconnect once, then contact [Sprid support](mailto:hello@sprid.studio) with the account name and error message if they still do not appear.\n- **Access denied:** confirm that the Pinterest account is active and that you completed the consent screen. Retry once; if Pinterest still refuses access, contact Sprid support.\n- **Publishing or scheduling requires Standard access:** the account can remain connected and its boards can still be selected during setup. Sprid will not create Trial-access Pins. Only Sprid can resolve its developer access tier.\n- **Analytics unavailable:** use a Pinterest business account and check that the Pin is public. Treat unavailable data as unavailable, never as zero.\n\n## Sources\n\n- [Pinterest developer access tiers](https://developer.pinterest.com/docs/key-concepts/access-tiers/)\n- [Pinterest developer guidelines](https://policy.pinterest.com/en/developer-guidelines)\n- [Claim your website](https://help.pinterest.com/en/business/article/claim-your-website)\n- [Pinterest Analytics](https://help.pinterest.com/en/business/article/pinterest-analytics)\n",
1134
- "revision": "e2c1b334eb3e495e"
1121
+ "markdown": "# Pinterest\n\n**What Sprid does with this:** Publish the Pins you select to the right boards, keep their destination links intact and read their results when analytics access is available.\n\n## You need\n\n- A Pinterest account. A free business account is recommended, because Pinterest Analytics requires one.\n- Access to the Sprid account that will own this channel.\n- A board topic in mind. Sprid can create the board during publishing.\n\nYou do **not** create a Pinterest developer app, request API access or copy a token. You only authorize your own Pinterest account.\n\n## Then run\n\n```sh\nsprid connect pinterest --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug, or use **Settings → Accounts → [account] → Pinterest → Continue with Pinterest** in Sprid.\n\n## Click path (the connect)\n\n1. Check the Pinterest identity in the browser that opens. Sign out first if it is the wrong account.\n2. Review the requested access and authorize Sprid.\n3. Back in Sprid, confirm the connected Pinterest name.\n4. Open a post, select **Publish → Pinterest** and pick the destination in the **Board** picker. If none fits, select **Create board**, name it and choose public or secret; Sprid selects it automatically.\n\nEach Sprid account needs its own connect, even when several Pinterest accounts are signed in to the same browser. Check the identity every time.\n\n## Check the connection\n\nRun `sprid status` and confirm Pinterest shows the intended account. Open a draft Pin, select **Publish → Pinterest** and confirm the intended board appears before approving any schedule.\n\nAfter the first Pin publishes, open Pinterest signed out or from another account and check the Pin, board, visual, title and destination. A successful API response does not prove public visibility.\n\n## Trial and Standard access\n\nPublishing and scheduling need **Sprid’s** Pinterest app to have Standard access. With Trial access you can connect and browse boards, but Sprid refuses to publish or schedule. This is Sprid’s approval, not something you apply for, and reconnecting does not change it. Keep preparing drafts, but do not treat them as booked.\n\n## Prepare the account\n\nCreate a few specific boards around topics people search for, such as “Small apartment viewing checklist” rather than “Inspiration”. Board names, descriptions and saved Pins give Pinterest context.\n\nClaim your website in Pinterest where possible: it links Pins from that site to the profile and expands website analytics. A website can be claimed by only one Pinterest account, so decide which market or brand owns it before connecting several.\n\nRead [Pinterest content and publishing](https://sprid.studio/docs/pinterest) before preparing the first batch.\n\n## If it fails\n\n- **“Pinterest is unavailable until API credentials and an access tier are configured”:** this Sprid deployment is not ready for Pinterest. Follow Sprid’s status or contact support; you do not supply credentials.\n- **Wrong account connected:** disconnect it in Sprid, sign out of Pinterest in that browser, then reconnect and check the identity before authorizing.\n- **“No Pinterest boards yet”:** create one from the board picker. If Sprid asks for board permission, reconnect once; older connections lack the board write scope.\n- **Existing boards missing:** reconnect once, then contact [Sprid support](mailto:hello@sprid.studio) with the account name and error.\n- **Access denied:** confirm the Pinterest account is active and you finished the consent screen. Retry once, then contact Sprid support.\n- **Publishing or scheduling requires Standard access:** see Trial and Standard access above. Boards stay selectable; only Sprid can change the tier.\n- **Analytics unavailable:** use a business account and check the Pin is public. Unavailable data is unavailable, not zero.\n\n## Sources\n\n- [Pinterest developer access tiers](https://developer.pinterest.com/docs/key-concepts/access-tiers/)\n- [Pinterest developer guidelines](https://policy.pinterest.com/en/developer-guidelines)\n- [Claim your website](https://help.pinterest.com/en/business/article/claim-your-website)\n- [Pinterest Analytics](https://help.pinterest.com/en/business/article/pinterest-analytics)\n",
1122
+ "revision": "a81732fc9409f861"
1135
1123
  }
1136
1124
  ];
1137
1125
  export const CONTENT_GUIDES = [
1138
1126
  {
1139
1127
  "id": "chat",
1140
1128
  "title": "Use Sprid in chat",
1141
- "summary": "Take an app from setup to a reviewed post and confirmed delivery using Sprid MCP and your browser. No CLI or installed skills are required for this flow.",
1129
+ "summary": "Take an app from setup to a reviewed post and confirmed delivery using only your chat app and a browser. No CLI or installed skills needed.",
1142
1130
  "url": "https://sprid.studio/docs/chat",
1143
- "markdown": "# Use Sprid in chat\n\n**What this guide does:** Take an app from setup to a reviewed post and confirmed delivery using Sprid MCP and your browser. No CLI or installed skills are required for this flow.\n\nRead [one marketing plan](../../../references/guided-marketing.md). Begin with `get_marketing_plan` when available and reuse its prepared artifact, accepted guidance and exact continuation. A browser user can start discovery from a website or store URL without creating a social account.\n\n## Connect Sprid via MCP\n\nChoose your chat at `https://sprid.studio/start#chat` and follow its connection steps, then tell your connected agent what you want to make. You do not need to learn the tool names below. They tell the agent how to continue: check capabilities, retrieve the relevant guide, follow the returned next action and ask you for missing inputs or approval. Setup should introduce only the connection or tool your current task needs.\n\nAdd `https://api.sprid.studio/api/mcp?surface=core` in your chat application's remote MCP connection settings. Sign in to Sprid and authorize the intended workspace. A host may call this an app, connector or integration. **Sprid MCP** is the remote service behind it. Sprid skills supply optional workflow instructions; the plugin bundles those skills and the MCP configuration. Sprid CLI adds terminal access and local production.\n\nCall `get_capabilities`, then `list_apps` and `list_accounts`. Reuse existing records. If your app is missing, describe it or supply its website/store URL, confirm its identity and create its App Profile using `upsert_app_profile`. A content account owns its voice and media; a channel is the exact social destination. Use the dashboard's account setup if one is missing. Never choose a handle just because it is first in a list.\n\n## Connect the destination\n\nRead `get_documentation` with the platform name. Use `connect_channel` and open its returned OAuth link in the browser. The person completes consent; `channel_status` checks the saved result. The CLI is optional.\n\nFor analytics or store credentials, open the secure App Profile form at `https://app.sprid.studio/settings/app-profiles`. Select the app and service, enter its identifiers and choose the key file or paste the key into that form. Never put credentials in chat, tool arguments or generated reports. `get_marketing_review` performs a live read; a saved-key indicator establishes configuration only.\n\n## Use images or a finished video\n\nDraft the copy in your current chat, using the content account's voice and the intended platform. Ordinary drafting does not require an additional Sprid model key.\n\nIf your host exposes the selected files, use `import_assets` with its file references. Each file has `download_url`, `file_id`, and optionally `mime_type` and `file_name`. Public HTTPS URLs and existing `{kind, id}` library references are also accepted. The limit is 32 MiB per imported file. PNG, JPEG, WebP and finished MP4 are supported. The result reports each position independently; retry missing files before assembling the full post. Files are copied to the selected account and deduplicated by bytes. Temporary download URLs are not retained as provenance.\n\nFor an image generated in ChatGPT, use the existing result when the host exposes a transferable file. Direct transfer of every generated-image type is unverified. If the host cannot transfer an image, download it and use the browser upload fallback. Do not ask the agent to recreate the image or transcribe binary data. A `sandbox:` URL or a local path cannot be fetched by remote MCP.\n\nFallback: call `create_upload_session` with the content account and a new UUID `requestId`. Open its authenticated browser link, select the files, then return to chat. Read `get_studio_job` with the returned ID. Its result keys are zero-based positions. The same session and asset IDs work in another chat client. Existing files incur no image-generation charge.\n\n## Create and review the post\n\nUse `create_post_from_assets` with a new UUID `requestId`, account, ordered `{kind, id}` assets, captions and aspect ratio. Keep the ID and inputs when retrying a lost response. `presentation: \"finished\"` preserves artwork without text overlays or added music; images fit inside the chosen output canvas, which can add margins. `presentation: \"editable\"` allows `text` and `subtitle` on image slides. One finished video becomes one reel. Never mix a finished video with image slides in this operation.\n\nRead the draft with `get_post`; edit captions using `update_post` and slide content using `update_slide` or `batch_update_slides`. Call `preview_post`. Inspect every returned slide or the video, and check captions, order, crop and legibility. Links work even when your host cannot show an embedded preview. A preview is not publication approval.\n\nFor photos or text that need to become a reel, inspect `get_capabilities` for hosted creation. `create_reel` accepts ordered scenes with `imageId`, `text` and `durationMs`, up to 120 seconds. It creates a silent finished MP4. Check `spend_status`, explain the render charge (3 cents per started render minute or the plan allowance), and obtain authorization before `confirmed: true`. Save its job ID and read `get_studio_job`; a retry with the same ID never starts a second render. A stopped job reports `needs_attention` rather than silently charging another attempt. Custom app capture and arbitrary local builds remain external inputs; upload their finished files.\n\n## Approve delivery and read the receipt\n\nOpen the returned `/p/<postId>` review link. The person reviews the actual media and captions, chooses exact channels and time, and completes platform-specific choices. TikTok privacy and interaction choices have no defaults. The existing posting screen owns those requirements. An agent-supplied confirmation flag is not evidence of a human click.\n\nAfter approval, read `get_publication_status`. Keep draft, scheduled, failed and delivered states separate. Report delivery only with its platform receipt. Read a failure before retrying; never repeat a publishing call merely because a response was lost.\n\nFor results, read the `marketing-review` guide through `get_documentation`. Return to the same app/account and post IDs across clients. Call `next_actions` for the next relevant need; an empty `verb` means follow its browser link.\n\n## Limits and troubleshooting\n\n- An inaccessible attachment needs the browser upload page, not a CLI installation.\n- A missing account or service needs secure dashboard setup. Never paste credentials into MCP.\n- Check current capabilities before promising hosted creation. App binaries, native simulator captures and arbitrary repository scripts still run outside remote MCP.\n- Store screenshot composition and store release commands remain explicit local capabilities unless `get_capabilities` reports a hosted implementation.\n- Uploaded-file and generated-file transfer require separate host integration checks. A supported schema alone does not prove a particular host exposes either file.\n",
1144
- "revision": "c2262fc4f30e9898"
1131
+ "markdown": "# Use Sprid in chat\n\n**What this guide does:** Take an app from setup to a reviewed post and confirmed delivery using only your chat app and a browser. No CLI or installed skills needed.\n\nYou tell the agent what you want; it handles the tool calls below. The tool names are here so the agent knows the order. Setup should only ask for the connection the current task needs.\n\n## Connect Sprid\n\nChoose your chat at `https://sprid.studio/start#chat` and follow its steps. Your chat may call this an app, connector or integration. The address is `https://api.sprid.studio/api/mcp?surface=core`. Sign in to Sprid and authorize the right workspace.\n\nAgent: when available, start with `get_marketing_plan` and reuse its prepared work and next step rather than starting over. Discovery can start from a website or store URL, with no social account.\n\n## Pick the app and account\n\nAgent: call `get_capabilities`, then `list_apps` and `list_accounts`, and reuse what exists. If the app is missing, get its description or website/store URL, confirm it and create it with `upsert_app_profile`. A content account owns voice and media; a channel is one exact social destination. Never pick a handle just because it is first in a list. A missing account is created in the dashboard.\n\n## Connect a destination or data source\n\n- **Social channel:** `get_documentation` with the platform name, then `connect_channel`. The person opens the returned link and approves in the browser; `channel_status` confirms the saved result.\n- **Store or analytics:** the person enters identifiers and the key in the secure form at `https://app.sprid.studio/settings/app-profiles`. Credentials never go in chat, tool arguments or reports. A saved-key indicator only proves configuration; `get_marketing_review` does a live read.\n\n## Bring images or a finished video\n\nDraft copy in the chat, in the account’s voice. No extra model key needed.\n\nIf the chat exposes the attached files, pass them to `import_assets`: each file has `download_url` and `file_id`, optionally `mime_type` and `file_name`. Public HTTPS URLs and existing `{kind, id}` library references also work. Limits: PNG, JPEG, WebP or finished MP4, up to 32 MiB each. Each position reports on its own; retry the missing ones before building the post. Files are copied to the account and deduplicated by bytes; temporary download URLs are not kept.\n\nA `sandbox:` URL or local path can’t be fetched. For an image the chat generated, use the existing file if the host exposes it (not verified for every host); otherwise download it and use the browser upload. Never ask the agent to recreate an image or transcribe binary data.\n\n**Fallback: browser upload.** Call `create_upload_session` with the account and a new UUID `requestId`. The person opens the link and picks the files. Then read `get_studio_job` with the returned ID; its result keys are zero-based positions. The same session and asset IDs work from any other chat. No image-generation charge.\n\n## Create and review the post\n\nCall `create_post_from_assets` with a new UUID `requestId`, the account, ordered `{kind, id}` assets, captions and aspect ratio. Reuse the same ID and inputs if a response is lost.\n\n- `presentation: \"finished\"` keeps artwork as-is: no text overlay, no added music. Images fit inside the canvas, which can add margins.\n- `presentation: \"editable\"` allows `text` and `subtitle` on image slides.\n- One finished video becomes one reel. Never mix a video with image slides.\n\nEdit with `update_post` (captions) and `update_slide` or `batch_update_slides` (slides). Call `preview_post` and check every slide or the video: captions, order, crop, legibility. Preview links work even if the chat can’t embed them. A preview is not approval to publish.\n\n## Make a reel from photos or text\n\nCheck `get_capabilities` for hosted creation first. `create_reel` takes ordered scenes (`imageId`, `text`, `durationMs`), up to 120 seconds, and makes a silent MP4. It costs 3 cents per started render minute, or uses the plan allowance: check `spend_status`, tell the person, and get their go-ahead before sending `confirmed: true`. Save the job ID and read `get_studio_job`. Retrying with the same ID never starts a second render; a stopped job reports `needs_attention` instead of charging again.\n\nApp captures, simulator recordings and your own build scripts run outside remote MCP. Upload their finished files.\n\n## Approve and confirm delivery\n\nThe person opens the returned `/p/<postId>` link, reviews the real media and captions, and picks the channels, time and platform options. TikTok privacy and interaction choices have no defaults and are made on that screen. An agent-supplied confirmation flag is not a human click.\n\nAfter approval, read `get_publication_status`. Keep draft, scheduled, failed and delivered apart, and report delivery only with the platform receipt. Read a failure before retrying. Never repeat a publish call just because a response was lost.\n\nFor results, read `get_documentation` for `marketing-review`. Keep the same app, account and post IDs across clients. `next_actions` gives the next step; an empty `verb` means follow its browser link.\n\n## Limits and troubleshooting\n\n- **Attachment can’t be read:** use the browser upload, not the CLI.\n- **Missing account or service:** set it up in the dashboard. Never paste credentials into chat.\n- **Hosted creation:** check `get_capabilities` before promising it. Store screenshots and store releases are local unless it reports otherwise.\n- **File transfer:** whether a host exposes uploaded or generated files has to be checked per host. A supported schema doesn’t prove it.\n",
1132
+ "revision": "ed4c47ca50f04d9d"
1145
1133
  },
1146
1134
  {
1147
1135
  "id": "local",
1148
1136
  "title": "Build locally and send to Sprid",
1149
- "summary": "Build content with your own tools, upload originals into Sprid, and continue editing, reviewing and measuring the same posts in chat or the dashboard.",
1137
+ "summary": "Build content with your own tools, upload the finished files to Sprid, and keep editing, reviewing and measuring the same posts in chat or the dashboard.",
1150
1138
  "url": "https://sprid.studio/docs/local",
1151
- "markdown": "# Build locally and send to Sprid\n\n**What this guide does:** Build content with your own tools, upload originals into Sprid, and continue editing, reviewing and measuring the same posts in chat or the dashboard.\n\nRead [one marketing plan](../../../references/guided-marketing.md). Preserve the shared action and artifact identity when moving local output into Sprid; login failure must leave the local result usable.\n\n## Name the work\n\n`sprid media` owns local production and file transfer. `sprid post` owns shared server posts. Sprid MCP reaches the same connected operations from chat; optional Sprid skills teach the method. A plugin packages the skills and MCP configuration.\n\n`sprid media` is the prefix for that local pipeline: `build`, `register`, `check`, `push` and `preview`. A numeric local slug stays local: shared preview is explicitly `sprid post preview --id <postId>`.\n\n## Build and upload\n\nUse your app's existing renderer or design tools. `sprid media init` configures built-in stills or simulator-screen recipes; `sprid media build <slug>` builds through that configuration. `sprid media check <slug>` checks the output. Trusted TS/JS configuration executes local code. Native captures and binary builds require the app's own environment.\n\nSign in with `sprid login`. For finished files, run:\n\n```sh\nsprid media upload slide-01.png slide-02.png --account my-account --json\n```\n\nThe upload goes directly to storage. Sprid checks the original bytes and returns ordered asset IDs. PNG, JPEG, WebP and MP4 imports are bounded at 32 MiB per file; the existing local `push` video path handles larger files under its own limits. The command prints its request ID before uploading. Resume with the same files in the same order and `--request-id <UUID>`; read saved results with `sprid media job <UUID>`. Do not reuse a session for a different ordered selection.\n\nCreate `draft.json` with the returned IDs:\n\n```json\n{\n \"account\": \"my-account\",\n \"requestId\": \"5e253644-d8a5-4b18-9cc9-d4d921874138\",\n \"title\": \"A concrete moment\",\n \"captionInstagram\": \"The caption, with hashtags at the end\",\n \"aspectRatio\": \"4:5\",\n \"presentation\": \"finished\",\n \"assets\": [{ \"kind\": \"image\", \"id\": 101 }, { \"kind\": \"image\", \"id\": 102 }]\n}\n```\n\nReplace the example request ID with a new UUID for each new draft, then keep it for retries. Use `kind: \"video\"` and one asset for a finished reel. Finished artwork gets no new overlay or music. Images fit inside the selected canvas; inspect any margins in the shared preview.\n\n```sh\nsprid post create --file draft.json --json\nsprid post get 123 --json\nsprid post update 123 --file changes.json --json\nsprid post preview --id 123 --json\nsprid post deliveries 123 --json\n```\n\n`changes.json` uses the existing post-update schema, for example `{\"captionInstagram\":\"Revised caption\"}`. Continue in chat by supplying post ID `123`; do not create another draft. The returned review link opens the same dashboard screen used from MCP. Human approval and platform choices remain explicit. Read delivery receipts before reporting success.\n\n## Hosted options and results\n\n`sprid capabilities --json` reports server limits and local-only tasks. `sprid media import --file sources.json` accepts the same account/files/urls/assets payload as MCP `import_assets`. `sprid media reel --file recipe.json` starts the explicit metered hosted recipe; the payload and approval requirements are in `sprid docs chat`. Poll with `sprid media job <UUID>`.\n\nUse `sprid marketing-review` for connected evidence, and the optional marketing-review skill to interpret it alongside repo changes. Secrets are handled by CLI key-file commands or secure Sprid forms, never conversations. Read `sprid docs marketing-review` for coverage and attribution rules.\n",
1152
- "revision": "426b71b33a6bec2d"
1139
+ "markdown": "# Build locally and send to Sprid\n\n**What this guide does:** Build content with your own tools, upload the finished files to Sprid, and keep editing, reviewing and measuring the same posts in chat or the dashboard.\n\nTwo command families: `sprid media` is local production and file transfer (`init`, `build`, `check`, `register`, `push`, `preview`, `upload`). `sprid post` works on shared posts on the server. If sign-in fails, the local result stays usable. Keep the same post and plan IDs when you move work into Sprid.\n\n## Build\n\nUse your app’s own renderer or design tools, or a built-in recipe:\n\n```sh\nsprid media init # set up the stills or simulator-screen recipe\nsprid media build <slug>\nsprid media check <slug>\n```\n\nA TS/JS config runs local code, so only use one you trust. Native captures and binary builds need your app’s own environment. A local slug stays local; a shared preview is always `sprid post preview --id <postId>`.\n\n## Upload\n\n```sh\nsprid login\nsprid media upload slide-01.png slide-02.png --account my-account --json\n```\n\nFiles go straight to storage and come back as ordered asset IDs. PNG, JPEG, WebP and MP4, up to 32 MiB each; larger videos go through `sprid media push`. The command prints a request ID first. To resume, rerun with the same files in the same order and `--request-id <UUID>`, or read the saved result with `sprid media job <UUID>`. Don’t reuse a request ID for a different selection.\n\n## Create and review the post\n\nWrite `draft.json` with the returned IDs and a new UUID as `requestId` (keep it for retries):\n\n```json\n{\n \"account\": \"my-account\",\n \"requestId\": \"5e253644-d8a5-4b18-9cc9-d4d921874138\",\n \"title\": \"A concrete moment\",\n \"captionInstagram\": \"The caption, with hashtags at the end\",\n \"aspectRatio\": \"4:5\",\n \"presentation\": \"finished\",\n \"assets\": [{ \"kind\": \"image\", \"id\": 101 }, { \"kind\": \"image\", \"id\": 102 }]\n}\n```\n\nFor a finished reel, use one asset with `kind: \"video\"`. `finished` artwork gets no overlay or music; images fit inside the canvas, so check the preview for margins.\n\n```sh\nsprid post create --file draft.json --json\nsprid post get 123 --json\nsprid post update 123 --file changes.json --json # e.g. {\"captionInstagram\":\"Revised caption\"}\nsprid post preview --id 123 --json\nsprid post deliveries 123 --json\n```\n\nThe review link opens the same dashboard screen as from chat. To continue in chat, give it post ID `123` instead of creating another draft. Approval and platform choices stay with a person. Check delivery receipts before reporting success.\n\n## Hosted options and results\n\n- `sprid capabilities --json`: server limits and what stays local.\n- `sprid media import --file sources.json`: the same account/files/urls/assets payload as MCP `import_assets`.\n- `sprid media reel --file recipe.json`: the metered hosted reel. Payload and approval rules are in `sprid docs chat`. Poll with `sprid media job <UUID>`.\n- `sprid marketing-review`: connected evidence. `sprid docs marketing-review` covers coverage and attribution.\n\nKeys go in through CLI key-file commands or the secure Sprid forms, never a conversation.\n",
1140
+ "revision": "9fca88d8e62aa719"
1153
1141
  },
1154
1142
  {
1155
1143
  "id": "marketing-review",
1156
1144
  "title": "Review marketing from connected evidence",
1157
- "summary": "Investigate acquisition, activation, retention and revenue through Sprid-held connections, with an explicit account of missing evidence when no repository is available.",
1145
+ "summary": "Investigate acquisition, activation, retention and revenue through the services connected to Sprid, and say plainly what evidence is missing.",
1158
1146
  "url": "https://sprid.studio/docs/marketing-review",
1159
- "markdown": "# Review marketing from connected evidence\n\n**What this guide does:** Investigate acquisition, activation, retention and revenue through Sprid-held connections, with an explicit account of missing evidence when no repository is available.\n\n## Start with the decision\n\nName the app, date range and question. Use `list_apps` and `list_app_profiles` to resolve identity. Read `get_marketing_review_context` for shared definitions, required investigations, prior decisions and corrections. Read `get_marketing_review` for the selected app and two comparable periods. Its default summary links to `view: \"full\", sources: [<source>]` evidence; omit `sources` to include social history, store reviews and milestones. Exact-window snapshots retain collection time. Read the exact saved packet through `get_marketing_review_evidence` with its `evidence.id`, or `sprid marketing-review evidence --id <id> --app <slug>`, even after a connection changes. `refresh: true` requests live reads; `cachedOnly: true` reads stored evidence only. Daily collection covers configured services on active plans, without model calls, publishing or paid social reads. Pause through the App Profile's `metricsCollectionEnabled: false` or `sprid marketing-review collection --enabled false`. Stored evidence remains readable. A configuration check does not verify provider access. Preserve returned errors, coverage, population definitions and sample counts.\n\nIn a browser chat, ask for a release summary or public changelog when the question concerns a shipped change. Label it supplied evidence. Do not claim to have inspected a repository or private app database. Local users can add repo diffs and measured database reads through their own authorized tools.\n\n## Follow the evidence\n\nUse `list_marketing_queries` to discover supported operations and their schemas; `query_marketing_source` executes those reads through Sprid-held credentials. Follow pagination and keep truncation visible. Read `get_documentation` with `topic: \"metrics\"` for definitions and refused calculations, and `topic: \"queries\"` for source-specific queries.\n\nSeparate acquisition from activation and people from events. Compare equal date windows and comparable populations. A post impression is not an app install; a download is not an activated user. Missing onboarding events require an instrumentation recommendation, not a guessed funnel. Keep revenue rails separate until the overlap check supports adding them.\n\nFor each finding, test an alternative explanation: changed tracking, traffic exclusions, attribution window, seasonality or a different audience mix. Observational before/after changes do not establish causality. If a difference is within normal variation or the sample is too small, say so and avoid ranking it as a result.\n\n## Keep the review portable\n\nSave selected non-secret definitions, investigations, decisions, corrections and release references through `save_marketing_review_context` when sharing context is authorized. Send `baseRevision` from the latest read. A conflict returns both proposals; reconcile before retrying. Stored context is an assertion with references, not independent verification. CLI parity: `marketing-review context --app <slug>` reads, `--file <context.json>` imports `{baseRevision,context}`. Keep local private notes local.\n\nUse `add_event` for confirmed product or marketing changes with the relevant areas and why. Save a completed review as an app-scoped `kind: \"note\"` event: label the review date and keep its conclusion, evidence references, coverage and next hypothesis in `meta`. Retrieve it through `list_events` in another client. `log_reflection` is specifically for a post's supplied performance statistics. Store no secrets or signed attachment URLs in either record.\n\nReport the decision first, then the evidence, limitations and the observation that would change it. Keep failed provider reads separate from successful empty data. Read `next_actions` for the relevant shared follow-up. A missing credential goes to the secure App Profile form or CLI key-file command; no provider MCP installation is required for a supported Sprid query.\n\n## Collection and coverage\n\nThe scheduler refreshes two rolling 30-day windows daily and re-reads late provider exports. Snapshots are private, app-scoped, configuration-bound and retained for 90 days; local evidence exports remain the durable report archive. Other windows are collected on demand. Missing days stay unknown, and incomplete store/GSC windows withhold percentage changes. Daily unique people must never be summed into a monthly population. Snapshot collection does not make unequal cohorts comparable or establish that a product change caused an outcome.\n\nA `configuration_only` result proves saved identifiers and key presence, not access. Diagnostics return a safe code, HTTP status when known, retryability and an operation-specific next step. Retry transient errors before recommending reconnection. Unsupported metrics and provider privacy thresholds remain explicit investigation limits.\n",
1160
- "revision": "9894c112e82dc295"
1147
+ "markdown": "# Review marketing from connected evidence\n\n**What this guide does:** Investigate acquisition, activation, retention and revenue through the services connected to Sprid, and say plainly what evidence is missing.\n\n## Start with the question\n\nName the app, the date range and the decision the review should inform.\n\n1. `list_apps` and `list_app_profiles` to find the app.\n2. `get_marketing_review_context` for shared definitions, required investigations, earlier decisions and corrections.\n3. `get_marketing_review` for the app over two comparable periods. The summary links to full evidence per source (`view: \"full\", sources: [<source>]`); omit `sources` to include social history, store reviews and milestones.\n4. Re-read an exact saved packet with `get_marketing_review_evidence` and its `evidence.id` (CLI: `sprid marketing-review evidence --id <id> --app <slug>`). It still works after a connection changes.\n\n`refresh: true` asks for live reads; `cachedOnly: true` reads only stored evidence. Keep returned errors, coverage, population definitions and sample counts in the report.\n\nIn a browser chat there is no repository. For a question about a shipped change, ask for a release summary or public changelog and label it as supplied. Never claim to have read a repository or private database. Local users can add repo diffs and database reads through their own tools.\n\n## Follow the evidence\n\n`list_marketing_queries` lists supported reads and their schemas; `query_marketing_source` runs them with Sprid’s saved credentials. Follow pagination and keep truncation visible. `get_documentation` with `metrics` covers definitions and refused calculations; with `queries`, source-specific reads.\n\n- Keep acquisition apart from activation, and people apart from events. A post impression is not an install; a download is not an activated user.\n- Compare equal windows and comparable populations.\n- No onboarding events means recommending instrumentation, not guessing a funnel.\n- Keep revenue sources separate until the overlap check says they can be added.\n\nFor each finding, test another explanation: tracking changes, traffic exclusions, attribution window, seasonality, a different audience mix. A before/after comparison doesn’t prove cause. If a difference is within normal variation or the sample is small, say so and don’t rank it.\n\n## Report and save\n\nLead with the decision, then the evidence, the limits and what would change the answer. Keep failed reads apart from reads that returned nothing. `next_actions` gives the follow-up. A missing credential goes to the secure App Profile form or a CLI key-file command; no provider MCP is needed for a supported query.\n\n- **Shared context:** when authorized, save non-secret definitions, investigations, decisions, corrections and release references with `save_marketing_review_context`, sending `baseRevision` from the latest read. A conflict returns both versions; reconcile before retrying. Saved context is a claim with references, not verification. CLI: `sprid marketing-review context --app <slug>` reads, `--file <context.json>` imports `{baseRevision,context}`. Private notes stay local.\n- **Changes:** log confirmed product or marketing changes with `add_event`, naming the areas they affect and why.\n- **The review itself:** save it as an app-scoped `kind: \"note\"` event with the date, conclusion, evidence references, coverage and next hypothesis in `meta`. Other clients read it with `list_events`.\n\nNever store secrets or signed attachment URLs in any of these.\n\n## Collection and coverage\n\n- Sprid refreshes two rolling 30-day windows daily for configured services on active plans, and re-reads late provider exports. No model calls, publishing or paid social reads. Other windows are read on demand.\n- Pause with the App Profile’s `metricsCollectionEnabled: false` or `sprid marketing-review collection --enabled false`. Stored evidence stays readable.\n- Snapshots are private to the app, tied to its configuration and kept 90 days. Save local evidence exports for a lasting archive.\n- Missing days stay unknown. Incomplete store or Search Console windows get no percentage change. Never sum daily unique people into a monthly count.\n- `configuration_only` means identifiers and a key are saved, not that access works. Errors return a safe code, the HTTP status when known, whether to retry and the next step. Retry transient errors before recommending a reconnect. Unsupported metrics and provider privacy thresholds are stated as limits.\n",
1148
+ "revision": "f5e9475cb0f8636e"
1161
1149
  },
1162
1150
  {
1163
1151
  "id": "traffic",
1164
1152
  "title": "Check whether a traffic spike is real",
1165
- "summary": "Tells you how to judge a sudden jump in website visitors, and how to remove a scraper from your numbers without removing customers. Read it before you act on a spike, and before you explain one.",
1153
+ "summary": "Tells you how to judge a sudden jump in website visitors, and how to remove a scraper from your numbers without removing customers. Read it before you act on a spike or explain one.",
1166
1154
  "url": "https://sprid.studio/docs/traffic",
1167
- "markdown": "# Check whether a traffic spike is real\n\n**What this guide does:** Tells you how to judge a sudden jump in website\nvisitors, and how to remove a scraper from your numbers without removing\ncustomers. Read it before you act on a spike, and before you explain one.\n\nA spike is not evidence of a bot, and a quiet baseline is not evidence of\npeople. Both mistakes cost the same: one makes you chase a channel that never\nexisted, the other makes you drop real readers from your own scoreboard. Decide\nwith the four reads below, in order, and keep the rule you save as narrow as the\nevidence you actually have.\n\nSprid raises this for you. When one day carries at least 35% of a 30-day\nperiod and runs at ten times the median day, `next` shows **Review that day**\nwith the date, the multiple and that day's pageviews per visitor beside the\nrest of the month. It is a prompt, not a verdict: nothing in a daily series\nseparates a crawl from a day that went well, which is what the rest of this\nguide is for.\n\n## Four reads, in order\n\n**1. Find the hours, not the day.** Get daily visitors for the period, then\nsplit the suspect day by hour. Marketing arrives across a day and decays over\nseveral. A scrape is a block: it starts, runs at a flat rate for a few hours and\nstops. If the day's total lands in four consecutive hours and the hours either\nside are ordinary, you are looking at one client, not an audience.\n\n**2. Divide pageviews by visitors, and do not trust it on its own.** A browser\nthat keeps no cookie is a new person on every request, so a crawl that reads\neach page once reports a huge visitor count at 1.0 pageviews each, and that is\nworth seeing. But a crawler that hits each URL several times does not look like\nthat at all: the September 2026 scrape ran at **1.60 pageviews a visitor\nagainst a baseline of 1.44** — higher than the real traffic it was hiding in.\nRead the number, let a flat 1.0 accuse, and never let a healthy-looking ratio\nacquit.\n\n**3. Ask for the compound group, never the dimensions separately.** `review_traffic`\nreturns whole groups — browser, version, OS, screen, timezone, referrer,\ncountry together — because adding up independent dimension counts double-counts\neveryone. One group carrying most of a day is the finding. Twelve groups sharing\nit evenly is not, unless they agree on something (see below).\n\n**4. Look at which pages were hit.** A crawl walks your sitemap: every locale,\nevery item, a handful of hits each, nothing concentrated. Real discovery is\nlopsided — a few pages take most of the traffic, and they are the pages that\nrank or were linked.\n\n## What the fingerprint actually looks like\n\nSort the fields by how hard they are to fake, and read the hard ones:\n\n- **Timezone against geography** is the strongest single tell. A browser\n reporting `Asia/Shanghai` while the IP geolocates across the United States,\n Singapore and Germany is one operator on rotating addresses. The address is\n rented; the container's clock is not.\n- **An odd viewport** — `800x600`, `1512x982`, `1280x720` — beats the user\n agent, because the user agent is the field they bother to spoof. Expect a\n plausible current Chrome on macOS with a screen no customer has.\n- **A blend is itself a signature.** One wave rotated across Chrome 118–120,\n Edge 119–120 and Firefox 120–121 so that no browser stood out — on one\n viewport, one timezone and one referrer. What they varied is noise. What they\n forgot to vary is the rule.\n- **Referrer `$direct`** on everything, with no search or social mixed in.\n- **Country is not the tell.** The same wave arrives from a dozen of them. A\n country rule removes customers and keeps the scraper.\n\n## The trap: your comparison period is contaminated too\n\nA percentage has two numbers in it, and the older one is the one nobody looks\nat. On one app in September 2026 the same thirty days read **+252.5%** before\nany review, **-62.6%** with only the obvious spike day excluded, and **-17.6%**\nonce two earlier waves in the comparison period were excluded as well. Same\ndata, three answers, two of them wrong, and the middle one was the most\nconvincing because it was already a correction.\n\nSo when you find one wave, look for its siblings before you save anything.\nQuery the whole visible history for the marker you just identified — the\ntimezone, the viewport — and read it by day. Waves have edges: a background of\n5 to 30 visitors a day with several hundred on the wave days tells you both\nwhere the rule starts and that the rest is ordinary.\n\n## Two more things that will catch you out\n\n**Raw provider data is not what Sprid counted.** Web visitors already exclude\nevents with no browser, crawler and headless user agents, reported bots, and\nanything marked internal or test. A wave that arrives with a null browser never\nreached your total, so a raw query that includes it will not reconcile with the\ncard. Compare like for like or you will exclude the same traffic twice.\n\n**Bound a rule as tightly as its conditions are ordinary.** `1920x1080` is a\nreal screen that real customers use, so a rule built on it gets the wave's dates\nand nothing wider. A viewport nobody has can carry a longer window. The date\nrange is the safety margin for a condition that is not distinctive on its own.\n\n## Saving the exclusion\n\nReview the range, preview the effect, then save. `review_traffic` previews\nvisitor counts before and after for the period **and the one before it**, which\nis the number you want to see move.\n\n```json\n{\n \"id\": \"scrape-2026-09-18\",\n \"reason\": \"Full-site crawl 04:00-08:00 UTC: Asia/Shanghai timezone, 800x600 viewport, direct referrer, IPs across US/SG/DE. 39,958 of the day's 40,476 visitors, every locale and item page hit at most 5 times with no cookie reuse.\",\n \"start\": \"2026-09-18\",\n \"end\": \"2026-09-19\",\n \"enabled\": true,\n \"conditions\": [\n { \"field\": \"timezone\", \"value\": \"Asia/Shanghai\" },\n { \"field\": \"screenWidth\", \"value\": \"800\" },\n { \"field\": \"screenHeight\", \"value\": \"600\" }\n ]\n}\n```\n\nEnd dates are exclusive and UTC. Conditions inside a rule are AND; separate\nrules are OR. Fields: `browser`, `browserVersion`, `os`, `osVersion`,\n`screenWidth`, `screenHeight`, `timezone`, `referrer`, `country`. Values are\nexact strings and a missing property never matches. Write the `reason` for\nsomeone who finds the rule in a year and has to decide whether it still holds —\nname the window, the evidence and the size of what it removes.\n\nSave it through `upsert_app_profile` or `PATCH /api/app-profiles/:id` under\n`posthogConfig.trafficExclusions`, preserving the rest of the configuration. Then\n**log the change as an event** with the areas it can explain and why, or the drop\nin your own graph becomes a second mystery a month from now.\n\n## What it does not touch\n\nYour provider's stored data is never changed or deleted; the exclusion is a\nfilter Sprid applies when it reads. Native app activity, registrations and\nrevenue are unaffected — a scraper does not sign up. Raw connected queries and\nthe marketing-review event inventory stay unfiltered on purpose, because they\nare the evidence you check the rule against. **Restore this traffic** disables a\nrule and the original numbers come straight back.\n\n## When not to exclude\n\nA spike with a real referrer and a lopsided page distribution is a link that\nworked. Find it before you filter it. A shared\noffice machine, an uptime probe and a preview renderer all produce a repeated\nfingerprint and none of them is worth a rule. And a group you cannot explain\nstays in the numbers: an unexplained visitor counted is a smaller error than a\ncustomer removed.\n",
1168
- "revision": "4b12201d75a57af1"
1155
+ "markdown": "# Check whether a traffic spike is real\n\n**What this guide does:** Tells you how to judge a sudden jump in website\nvisitors, and how to remove a scraper from your numbers without removing\ncustomers. Read it before you act on a spike or explain one.\n\nA spike is not proof of a bot, and a quiet baseline is not proof of people.\nGet it wrong one way and you chase a channel that never existed; the other way\nand you drop real readers from your own numbers. Use the four reads below, in\norder, and keep any rule as narrow as your evidence.\n\nSprid flags candidates. When one day carries at least 35% of a 30-day period\nand runs at ten times the median day, `next` shows **Review that day** with the\ndate, the multiple and that day’s pageviews per visitor against the rest of the\nmonth. It is a prompt, not a verdict: a daily series can’t tell a crawl from a\ngood day.\n\n## Four reads, in order\n\n**1. Find the hours, not the day.** Split the suspect day by hour. Marketing\narrives across a day and decays over several. A scrape is a block: a flat rate\nfor a few hours, then nothing. If the day’s total sits in four consecutive\nhours with ordinary hours either side, it is one client, not an audience.\n\n**2. Divide pageviews by visitors, but don’t trust it alone.** A browser with no\ncookie is a new visitor on every request, so a crawl that reads each page once\nshows a huge visitor count at 1.0 pageviews each. A crawler that hits each URL\nseveral times doesn’t: the September 2026 scrape ran at **1.60 pageviews a\nvisitor against a baseline of 1.44**. A flat 1.0 is suspicious; a healthy ratio\nproves nothing.\n\n**3. Ask for whole groups, not separate dimensions.** `review_traffic` returns\ncompound groups (browser, version, OS, screen, timezone, referrer, country\ntogether), because adding up separate dimension counts double-counts everyone.\nOne group carrying most of a day is the finding. Twelve groups sharing it evenly\nis not, unless they agree on something (below).\n\n**4. Look at which pages were hit.** A crawl walks your sitemap: every locale\nand item, a few hits each. Real discovery is lopsided: a few pages that rank or\nwere linked take most of the traffic.\n\n## What the fingerprint looks like\n\nRead the fields that are hardest to fake:\n\n- **Timezone against geography** is the strongest tell. A browser on\n `Asia/Shanghai` while its IPs sit in the US, Singapore and Germany is one\n operator on rented addresses. The addresses rotate; the machine’s clock\n doesn’t.\n- **An odd viewport** (`800x600`, `1512x982`, `1280x720`) beats the user agent,\n which is the field they bother to spoof. Expect a plausible current Chrome on\n macOS with a screen no customer has.\n- **A blend is a signature too.** One wave rotated Chrome 118-120, Edge 119-120\n and Firefox 120-121 so no browser stood out, all on one viewport, timezone and\n referrer. What they varied is noise; what they forgot to vary is the rule.\n- **Referrer `$direct`** on everything, with no search or social.\n- **Country is not a tell.** The same wave comes from a dozen countries. A\n country rule removes customers and keeps the scraper.\n\n## Check the comparison period too\n\nA percentage change has two periods, and nobody looks at the older one. On one\napp in September 2026 the same thirty days read **+252.5%** before review,\n**-62.6%** with only the obvious spike excluded, and **-17.6%** once two earlier\nwaves in the comparison period were excluded too. The middle answer was wrong\nand the most convincing, because it already looked corrected.\n\nSo when you find one wave, look for others before saving anything. Query the\nwhole history for its marker (the timezone, the viewport) by day. Waves have\nedges: 5 to 30 visitors a day in the background and several hundred on wave\ndays tells you where the rule starts and that the rest is ordinary.\n\n## Two more traps\n\n**Raw provider data is not what Sprid counted.** Web visitors already exclude\nevents with no browser, crawler and headless user agents, reported bots, and\nanything marked internal or test. A wave with a null browser never reached your\ntotal, so a raw query including it won’t match the card. Compare like with like\nor you exclude the same traffic twice.\n\n**Bound a rule by how ordinary its conditions are.** `1920x1080` is a real\nscreen many customers use, so a rule on it gets exactly the wave’s dates. A\nviewport nobody has can carry a longer window. The date range is the safety\nmargin for a condition that isn’t distinctive on its own.\n\n## Save the exclusion\n\nPreview, then save. `review_traffic` shows visitor counts before and after for\nthe period **and the one before it**, which is the number you want to see move.\n\n```json\n{\n \"id\": \"scrape-2026-09-18\",\n \"reason\": \"Full-site crawl 04:00-08:00 UTC: Asia/Shanghai timezone, 800x600 viewport, direct referrer, IPs across US/SG/DE. 39,958 of the day's 40,476 visitors, every locale and item page hit at most 5 times with no cookie reuse.\",\n \"start\": \"2026-09-18\",\n \"end\": \"2026-09-19\",\n \"enabled\": true,\n \"conditions\": [\n { \"field\": \"timezone\", \"value\": \"Asia/Shanghai\" },\n { \"field\": \"screenWidth\", \"value\": \"800\" },\n { \"field\": \"screenHeight\", \"value\": \"600\" }\n ]\n}\n```\n\n- End dates are exclusive and UTC.\n- Conditions in a rule are AND; separate rules are OR.\n- Fields: `browser`, `browserVersion`, `os`, `osVersion`, `screenWidth`,\n `screenHeight`, `timezone`, `referrer`, `country`. Values are exact strings;\n a missing property never matches.\n- Write `reason` for someone reading it in a year: the window, the evidence and\n how much it removes.\n\nSave through `upsert_app_profile` or `PATCH /api/app-profiles/:id` under\n`posthogConfig.trafficExclusions`, keeping the rest of the configuration. Then\n**log the change as an event** with the areas it affects and why, or next\nmonth’s drop in your graph becomes a new mystery.\n\n## What an exclusion doesn’t touch\n\nYour provider’s data is never changed or deleted; Sprid filters when it reads.\nApp activity, registrations and revenue are unaffected, since a scraper doesn’t\nsign up. Raw connected queries and the marketing-review event inventory stay\nunfiltered on purpose, as the evidence to check the rule against. **Restore this\ntraffic** disables a rule and the original numbers come straight back.\n\n## When not to exclude\n\nA spike with a real referrer and lopsided pages is a link that worked: find it\nbefore you filter it. A shared office machine, an uptime probe and a preview\nrenderer all repeat a fingerprint, and none is worth a rule. A group you can’t\nexplain stays in: an unexplained visitor counted is a smaller error than a\ncustomer removed.\n",
1156
+ "revision": "2ee50917e61c94f4"
1169
1157
  },
1170
1158
  {
1171
1159
  "id": "pinterest-content",
1172
1160
  "title": "Pinterest content and publishing",
1173
1161
  "summary": "Plan searchable Pins, approve a useful batch, let Sprid publish the selected Pins on schedule and judge the result by qualified traffic and activation.",
1174
1162
  "url": "https://sprid.studio/docs/pinterest",
1175
- "markdown": "# Pinterest content and publishing\n\n**What this guide does:** Plan searchable Pins, approve a useful batch, let Sprid publish the selected Pins on schedule and judge the result by qualified traffic and activation.\n\nChecked against Pinterest’s current guidance on 21 September 2026. Recommendations labelled as tests are starting hypotheses, not claims about what will win for your account.\n\n## How Pinterest discovery works\n\nPinterest works as a visual search and recommendation system. A Pin can appear in search, home feeds and related recommendations long after publication. Discovery does not depend primarily on followers.\n\nPinterest says relevance comes from signals in the Pin and how people interact with it. Clear keywords, original imagery, link quality and relevant board context help Pinterest understand the subject; Pinterest does not publish a fixed weighting for them. Give one coherent answer to “what is this about?” across the visual, title, description, board and landing page. Use the language a person would search, without repeating a keyword unnaturally.\n\nPins can remain discoverable after publication. Compare results at equal Pin ages, and keep older useful Pins in the analysis instead of treating the first day as the full result.\n\n## Start with the destination\n\nChoose the page and desired user action before making the visual:\n\n1. Pick one useful guide, tool, feature page or store destination.\n2. State the concrete question it answers.\n3. Choose one action that continues the same task, such as opening the relevant feature or starting its setup.\n4. Add campaign parameters without deleting existing URL parameters. Use a stable Pin or creative identifier so visits can be traced back to the exact Pin.\n\nThe destination must deliver the pictured promise immediately and work well on a phone. A specific Pin leading to a generic homepage usually breaks that promise. A store visit is not an install, and an outbound click is not activation; keep each step separate in reporting.\n\n## Choose topics and boards\n\nUse Pinterest Trends, Pinterest search suggestions, customer language and proven site queries to build a topic list. Search Console is a useful seed, but Google demand does not prove Pinterest demand.\n\nMix evergreen questions with relevant seasonal moments. Use a focused board whose name and description explain the topic. A Pin about apartment-viewing costs belongs on a board about buying an apartment, not a broad “Ideas” board. Create a new board only when the topic will support a useful collection.\n\n## Choose a cadence to test\n\nSprid recommends **2 original Pins per day as an initial experiment**, spread across separate slots. With a broad catalog of useful destinations and genuinely distinct assets, test **5 per day** against that baseline. These are proposed test settings, not Pinterest limits or proven optima. A limited destination library calls for expanding useful content, not filling the queue with cosmetic duplicates.\n\nCreate and review batches together, then spread publication across the calendar. Rotate destination pages and topics. Start with two distinct creative treatments per destination; space those treatments across different days. Neither an exact spacing interval nor a universal best hour is established by the evidence reviewed.\n\nPinterest itself recommends original content at least weekly. Marketer examples range from one daily Pin to several: [Sarah Hanford](https://www.sarahhanford.com/blog/pinterest-growth-organically-60-days) reports moving from three daily to five, alongside keyword research and a new website. That does not isolate frequency as the cause of growth.\n\n[Tailwind’s benchmark](https://www.tailwindapp.com/pinterest-marketing/research/2025-benchmark-study-part-1) covered approximately 1.2 million organic Pins, using 2024 data. Results were concentrated in a small share of Pins. Its English-speaking customer sample and observational design cannot establish a universal cadence or predict another market’s conversions.\n\nReview cohorts at 30, 60 and 90 days of Pin age. Change one variable per test and repeat across comparable destinations. Keep higher frequency when additional Pins repeatedly produce additional qualified visits or activations; a lower click rate alone does not invalidate growth in total qualified traffic. Report counts and coverage, including results with the leading Pin removed, so one outlier cannot determine the strategy.\n\n## Build the Pin\n\nFor an image Pin, use **2:3**, ideally **1000 × 1500 px**. For full-screen video, use **9:16**, ideally **1080 × 1920 px**. Pinterest currently allows organic video from **4 seconds to 5 minutes** with H.264 or H.265 encoding.\n\nSprid publishes one selected image or one finished video per Pin. It does not silently turn a multi-slide Instagram carousel into a Pinterest Pin. Prepare a dedicated Pinterest asset and inspect its crop in the preview.\n\nAt feed-thumbnail size, the subject and benefit should still be clear. Use one visual focus, strong contrast and brief readable overlay copy. Check the real phone preview. The Pin must stand alone; an Instagram opener that only makes sense after swiping to slide two is incomplete here.\n\nUseful formats to test include:\n\n- an editorial cover for a specific guide;\n- a reference card with a concrete checklist or answer;\n- a real product or app demonstration showing one task;\n- a coherent visual collection for design, travel or architecture;\n- a short captioned video when motion explains the task better than a still.\n\nPinterest can label detected or declared AI imagery. Preserve provenance, use accurate licensed assets and make any required disclosure. Never publish an image that misrepresents the linked place, product or result.\n\n## Write the metadata\n\nOrganic Pin titles allow **100 characters**. Put the useful subject early because only part of the title may appear in a feed. Descriptions allow **800 characters**. Descriptions may be hidden in home and search feeds, but Pinterest uses them to understand relevance.\n\nAlt text allows **500 characters** in the Pinterest API. Describe what the visual shows for someone who cannot see it; do not use alt text as another keyword field. Choose an AI disclosure in Sprid when it applies to the asset.\n\nWrite a natural title and description that name the topic, audience or situation and what the destination provides. Choose a relevant board and add a working destination. Do not stuff variants of the same keyword, use unrelated trends or promise something the page does not deliver.\n\nThe visual earns attention; metadata supplies context; the destination completes the task. Keep all three aligned.\n\n## Publish your first Pin\n\nPublishing and scheduling require Sprid’s Pinterest app to have Standard access. With Trial access, you can connect the account, browse boards and prepare drafts, but Sprid will refuse the publish or schedule request. This access tier belongs to Sprid’s integration; the customer does not apply for a developer app.\n\n1. Connect Pinterest, then run `sprid status` and confirm the intended account.\n2. Create or open a post with exactly one image, preferably at 2:3, or one finished video, preferably at 9:16.\n3. Open **Publish**, select **Pinterest**, then choose a **Board** and optional section.\n4. Add the Pin title, description, destination, alt text and any AI disclosure.\n5. Inspect the rendered creative and every public field. Publish now or choose a future time.\n6. After delivery, open the Pin while signed out or from another account and follow its destination.\n\nIn the CLI, use `sprid pinterest boards --account <slug>` to find board IDs. If there are none, it says so; create one with `sprid pinterest create --account <slug> --name <name> [--privacy public|secret]`. Then use `sprid pinterest set <postId> --board <id> --link <url>` with the optional metadata flags shown by `sprid docs cli`. The command saves the draft; review and schedule it in Sprid. Later, `sprid pinterest metrics <publishId> --account <slug>` reads a bounded results window.\n\nWith MCP, read `get_documentation` topic `pinterest-content`, select the exact channel from `list_connections`, then call `list_pinterest_boards`. If it returns no boards, say so and call `create_pinterest_board` only with a user-approved name and privacy. Save `pinterestOptions` with `update_post`. Prefer `aspectRatio: \"2:3\"` for a new image Pin. Render the preview, show the user the specific Pin or dry-run batch, then call `schedule_post` or commit `schedule_batch` only for the Pins they selected. Use `get_pinterest_metrics` for results.\n\n## Review and schedule in Sprid\n\nFor a two-Pin daily test, choose **Daily** and two separate times in the batch scheduler. For example, **09:00 and 17:00** are convenient slots, not researched best posting times. Check the returned account timezone and the actual dates before confirming; do not assume they match your device. Keep an existing user-chosen cadence unless they ask to change it.\n\nOrder the selected Pins so different destinations alternate. Review the calendar preview for occupied days, gaps and any unplaced Pins. A large batch is fine to prepare at once; its publication can be spread across the schedule.\n\nFor MCP, use `schedule_batch` with `cadence: \"daily\"`, `times: [\"09:00\", \"17:00\"]`, the chosen account/channel and selected post IDs. Start with `dryRun: true`, then inspect `placements`, `unplaced` and `timezone`. Busy days are skipped by default, so check the returned plan rather than promising a fixed completion date. Commit only the reviewed selections and report what was actually booked. CLI users can read this guide with `sprid docs pinterest-content` and review the queue with `sprid queue`; use the scheduling UI or connected MCP tools to place the batch.\n\nA time-only move keeps the reviewed creative and destination. If you change a scheduled Pin’s content, board or link, review and reschedule the updated Pin. Changed drafts can be blocked at publication until reviewed again. After an ambiguous publishing failure, check Pinterest before retrying because the original Pin may already exist.\n\nReview every selected Pin’s rendered creative, public metadata, destination, board and scheduled time. Check factual claims, rights and disclosure before approving the batch.\n\nPinterest’s published developer guidelines require the user to choose each Pin that will be published. Sprid implements that rule by showing the concrete Pins and their schedule, then recording the user’s batch selection. Those selected Pins publish automatically at their scheduled times; the user does not return to approve the same unchanged schedule again. Review any new or edited Pin before adding it to a schedule.\n\nAfter the first scheduled Pin publishes, verify its public visibility and destination from outside the connected Pinterest account. A draft, preview or accepted schedule is not proof of delivery.\n\n## Measure what happened\n\nKeep Pinterest’s metrics distinct:\n\n- **Impressions:** times the Pin was on screen.\n- **Saves:** times people saved it to a board.\n- **Pin clicks:** clicks that opened the Pin in close-up.\n- **Outbound clicks:** actions leading to a destination outside Pinterest.\n- **Video views:** at least 2 seconds with at least 50% of the video in view.\n\nFor app growth, the useful path is **impression → outbound click → qualified landing visit → app action → activation**. Use saves as a diagnostic, not the final success metric. A Pin with many saves and few outbound clicks may be valuable as an on-platform reference while contributing little acquisition.\n\nCompare Pins of similar age within the same topic, language and market. Judge creative using outbound-click rate and qualified visits; judge the whole path using activations per Pin and, where available, revenue. Pinterest-reported outbound clicks and first-party website sessions use different measurement methods, so show both rather than forcing them to match.\n\nIf visits arrive but activation does not, inspect page speed, message match and the next app action before making more Pins. If impressions are low, inspect topic relevance, board context, metadata and visual clarity. If qualified traffic and activation remain negligible across repeated relevant cohorts, stop scaling that app’s Pinterest output.\n\n## Improve the next batch\n\nChange one meaningful element within a comparison: visual treatment, question, format or destination angle. Organic distribution is not randomized, so call the result a directional test, not a controlled A/B experiment.\n\nCreate fresh, useful treatments rather than cosmetic duplicates. [Tailwind’s freshness study](https://www.tailwindapp.com/pinterest-marketing/research/2025-benchmark-study-part-three) found stronger retained distribution from new images for an existing URL than from repeatedly reusing the same image and URL; this is association, not a guaranteed lift. Pinterest advises against repeatedly uploading the same Pin, and repetitive or irrelevant commercial content can be treated as spam. Spread treatments of the same destination apart and keep every Pin on a relevant board.\n\nUse the next review to choose topics and destinations for another test. For example, [Elaine Timms’s recipe-client case](https://elainetimms.com/evergreen-growth-how-food-creator-pins-for-profit/) combines search-specific overlays, creative variants and email opt-ins. Adopt the connected workflow; its reported growth cannot be attributed to cadence alone.\n\n## Prepare the website\n\nClaim an owned website in Pinterest to associate the profile with Pins from that domain and expand website analytics. One website can be claimed by only one Pinterest account.\n\nFor eligible articles, products or recipes, add the relevant Open Graph or Schema.org metadata for Rich Pins. Rich Pins synchronize information from the page; they are website metadata, not a separate Sprid export. Check how that metadata appears before publishing a batch because a manual Pin edit can override synced article or recipe information.\n\nMake sure Pinterestbot can read the destination, links work and pages load quickly on mobile. Preserve any existing URL parameters when adding attribution.\n\n## Sources\n\n- [How Pinterest discovery works](https://create.pinterest.com/blog/how-to-increase-discoverability-seo-pinterest/)\n- [Growing through search, boards and keywords](https://create.pinterest.com/blog/best-ways-to-grow-on-pinterest/)\n- [Pin specifications](https://help.pinterest.com/en/article/review-pin-specs)\n- [Pin performance and distribution](https://help.pinterest.com/en/business/article/pin-performance-and-distribution)\n- [Pinterest Analytics definitions](https://help.pinterest.com/en/business/article/pinterest-analytics)\n- [Pinterest developer guidelines](https://policy.pinterest.com/en/developer-guidelines)\n- [Pinterest API schema](https://github.com/pinterest/api-description/blob/main/v5/openapi.json)\n- [Pinterest community guidelines](https://policy.pinterest.com/en/community-guidelines)\n- [Pinterest labels for AI-generated or modified images](https://help.pinterest.com/en/article/gen-ai-labels)\n- [Claim your website](https://help.pinterest.com/en/business/article/claim-your-website)\n- [Rich Pins](https://help.pinterest.com/en-gb/business/article/rich-pins)\n",
1176
- "revision": "2426b956266bf702"
1163
+ "markdown": "# Pinterest content and publishing\n\n**What this guide does:** Plan searchable Pins, approve a useful batch, let Sprid publish the selected Pins on schedule and judge the result by qualified traffic and activation.\n\nChecked against Pinterest’s guidance on 21 September 2026. Anything labelled a test is a starting hypothesis, not a claim about what will win for your account.\n\n## How Pinterest discovery works\n\nPinterest is visual search and recommendation. A Pin can show up in search, home feeds and related Pins long after it is published, mostly regardless of followers.\n\nRelevance comes from the Pin itself and how people interact with it: clear keywords, original imagery, link quality and board context. Pinterest publishes no weighting. Make the visual, title, description, board and landing page all answer the same “what is this about?”, in the words a person would search, without repeating a keyword unnaturally.\n\nBecause Pins keep getting found, compare them at equal ages and keep older Pins in the analysis.\n\n## Start with the destination\n\nPick the page and the action before making the visual:\n\n1. One useful guide, tool, feature page or store destination.\n2. The concrete question it answers.\n3. One next action that continues the same task, such as opening that feature.\n4. Campaign parameters added without removing existing ones, with a stable Pin or creative ID so visits trace back to the exact Pin.\n\nThe page must deliver the pictured promise immediately and work on a phone. A specific Pin that lands on a generic homepage breaks the promise. A store visit is not an install, and an outbound click is not activation: report each step separately.\n\n## Prepare the website\n\n- **Claim your website** in Pinterest to link the profile to Pins from that domain and get website analytics. A website can be claimed by only one Pinterest account.\n- **Rich Pins:** for articles, products or recipes, add Open Graph or Schema.org metadata. Pinterest syncs it from the page; it is not a Sprid export. Check how it appears before a batch, because editing a Pin by hand can override synced article or recipe details.\n- Make sure Pinterestbot can read the page, links work and it loads fast on mobile.\n\n## Choose topics and boards\n\nBuild the topic list from Pinterest Trends, Pinterest search suggestions, customer language and site queries that already work. Search Console is a useful seed, but Google demand doesn’t prove Pinterest demand.\n\nMix evergreen questions with relevant seasonal moments. Use focused boards whose name and description explain the topic: a Pin about apartment-viewing costs belongs on a board about buying an apartment, not a broad “Ideas” board. Only create a board when the topic can fill it.\n\n## Build the Pin\n\n| | Ratio | Ideal size | Notes |\n|---|---|---|---|\n| Image | 2:3 | 1000 × 1500 px | |\n| Video | 9:16 | 1080 × 1920 px | 4 seconds to 5 minutes, H.264 or H.265 |\n\nSprid publishes one image or one finished video per Pin. It never turns an Instagram carousel into a Pin, so make a dedicated asset and check its crop in the preview.\n\nAt thumbnail size the subject and benefit should still be clear: one focus, strong contrast, short readable overlay copy. Check the real phone preview. The Pin must stand alone; an opener that only makes sense after swiping is incomplete here.\n\nFormats worth testing:\n\n- an editorial cover for a specific guide\n- a reference card with a concrete checklist or answer\n- a real app demo showing one task\n- a coherent visual collection for design, travel or architecture\n- a short captioned video when motion explains the task better\n\nPinterest can label detected or declared AI imagery. Keep provenance, use accurate licensed assets and disclose where required. Never publish an image that misrepresents the linked place, product or result.\n\n## Write the metadata\n\n| Field | Limit | Notes |\n|---|---|---|\n| Title | 100 characters | Put the subject early; feeds may cut it. |\n| Description | 800 characters | Often hidden in feeds, but Pinterest reads it for relevance. |\n| Alt text | 500 characters | Describe what the image shows. Not a keyword field. |\n\nWrite a natural title and description naming the topic, audience or situation and what the destination provides. Choose a relevant board, add a working destination, and set the AI disclosure in Sprid when it applies. No stuffed keyword variants, unrelated trends or promises the page doesn’t keep.\n\nThe visual earns attention, the metadata gives context, the destination completes the task. Keep all three aligned.\n\n## Publish your first Pin\n\nPublishing and scheduling need Sprid’s Pinterest app to have Standard access. With Trial access you can connect, browse boards and prepare drafts, but Sprid refuses to publish or schedule. That access belongs to Sprid’s integration; you don’t apply for a developer app.\n\n1. Connect Pinterest, run `sprid status` and confirm the account.\n2. Create or open a post with exactly one image (ideally 2:3) or one finished video (ideally 9:16).\n3. Open **Publish**, select **Pinterest**, then pick a **Board** and optional section.\n4. Add the title, description, destination, alt text and any AI disclosure.\n5. Check the rendered creative and every public field. Publish now or pick a time.\n6. After delivery, open the Pin signed out or from another account and follow its link.\n\n**CLI:** `sprid pinterest boards --account <slug>` lists board IDs, or says there are none; create one with `sprid pinterest create --account <slug> --name <name> [--privacy public|secret]`. `sprid pinterest set <postId> --board <id> --link <url>` saves the draft (optional metadata flags in `sprid docs cli`); review and schedule it in Sprid. `sprid pinterest metrics <publishId> --account <slug>` reads results.\n\n**MCP:** read `get_documentation` topic `pinterest-content`, pick the exact channel from `list_connections`, then `list_pinterest_boards`. If there are none, say so, and call `create_pinterest_board` only with a name and privacy the user approved. Save `pinterestOptions` with `update_post`, preferring `aspectRatio: \"2:3\"` for a new image Pin. Show the user the rendered Pin or dry-run batch, then call `schedule_post` or commit `schedule_batch` only for the Pins they chose. `get_pinterest_metrics` reads results.\n\n## Choose a cadence to test\n\nStart with **2 original Pins a day** in separate slots. With a broad catalog of useful destinations and genuinely distinct assets, test **5 a day** against that. These are Sprid’s proposed tests, not Pinterest limits or proven optimums. A small destination library needs more useful content, not cosmetic duplicates in the queue.\n\nRotate destinations and topics. Start with two distinct treatments per destination, on different days. No exact spacing or best hour is established.\n\nWhat the evidence says: Pinterest recommends original content at least weekly. [Sarah Hanford](https://www.sarahhanford.com/blog/pinterest-growth-organically-60-days) went from three Pins a day to five alongside keyword research and a new website, so frequency isn’t isolated as the cause. [Tailwind’s benchmark](https://www.tailwindapp.com/pinterest-marketing/research/2025-benchmark-study-part-1) of about 1.2 million organic Pins (2024 data) found results concentrated in a small share of Pins; its English-speaking, observational sample can’t set a universal cadence.\n\n## Review and schedule the batch\n\nFor a two-a-day test, choose **Daily** and two times in the batch scheduler, for example **09:00 and 17:00** (convenient, not researched best times). Check the account’s timezone and the actual dates before confirming; they may differ from your device. Keep a cadence the user already chose unless they ask to change it.\n\nOrder Pins so destinations alternate, and check the calendar preview for busy days, gaps and unplaced Pins. Preparing a large batch at once is fine; publication spreads across the schedule.\n\n**MCP:** `schedule_batch` with `cadence: \"daily\"`, `times: [\"09:00\", \"17:00\"]`, the account/channel and the selected post IDs. Run `dryRun: true` first and check `placements`, `unplaced` and `timezone`. Busy days are skipped by default, so read the returned plan instead of promising an end date. Commit only the reviewed Pins and report what was booked. CLI users read this guide with `sprid docs pinterest-content` and the queue with `sprid queue`; place batches in the scheduling screen or over MCP.\n\nBefore approving, check every Pin’s creative, metadata, destination, board and time, plus factual claims, rights and disclosure. Pinterest’s developer guidelines require the user to choose each Pin that publishes: Sprid shows the concrete Pins and schedule and records the selection. Those Pins then publish on time without asking again.\n\n- Moving only the time keeps the reviewed Pin. Changing content, board or link means reviewing and rescheduling it; changed drafts can be blocked at publish until reviewed.\n- After an unclear publish failure, check Pinterest before retrying; the Pin may already exist.\n- After the first Pin publishes, check it and its link from outside the connected account. A draft, preview or accepted schedule is not delivery.\n\n## Measure what happened\n\n- **Impressions:** times the Pin was on screen.\n- **Saves:** times people saved it to a board.\n- **Pin clicks:** opens of the Pin in close-up.\n- **Outbound clicks:** clicks through to a destination outside Pinterest.\n- **Video views:** at least 2 seconds with at least 50% of the video in view.\n\nFor app growth the path is **impression → outbound click → qualified visit → app action → activation**. Saves are a diagnostic, not the goal: a Pin with many saves and few outbound clicks may be a good reference on Pinterest and bring little acquisition.\n\nCompare Pins of similar age within the same topic, language and market. Judge creative by outbound-click rate and qualified visits, the whole path by activations per Pin and revenue where available. Pinterest’s outbound clicks and your own site sessions are measured differently; show both rather than forcing them to match.\n\nReview cohorts at 30, 60 and 90 days of Pin age. Report counts and coverage, including results with the top Pin removed, so one outlier can’t set the strategy. Keep a higher frequency while extra Pins keep adding qualified visits or activations; a lower click rate alone doesn’t cancel growth in total qualified traffic.\n\n- Visits but no activation: check page speed, message match and the next app action before making more Pins.\n- Low impressions: check topic relevance, board, metadata and visual clarity.\n- Negligible qualified traffic and activation across repeated relevant cohorts: stop scaling Pinterest for that app.\n\n## Improve the next batch\n\nChange one meaningful thing per comparison (visual treatment, question, format or destination angle) and repeat across comparable destinations. Organic distribution isn’t randomized, so call it a directional test, not an A/B test.\n\nMake fresh, useful treatments, not cosmetic duplicates. [Tailwind’s freshness study](https://www.tailwindapp.com/pinterest-marketing/research/2025-benchmark-study-part-three) found new images for an existing URL kept distribution better than reusing the same image and URL (an association, not a guaranteed lift). Pinterest advises against re-uploading the same Pin, and repetitive or irrelevant commercial content can be treated as spam. Space treatments of the same destination apart and keep every Pin on a relevant board.\n\nFor a worked example, [Elaine Timms’s recipe-client case](https://elainetimms.com/evergreen-growth-how-food-creator-pins-for-profit/) combines search-specific overlays, creative variants and email opt-ins. Its growth can’t be credited to cadence alone.\n\n## Sources\n\n- [How Pinterest discovery works](https://create.pinterest.com/blog/how-to-increase-discoverability-seo-pinterest/)\n- [Growing through search, boards and keywords](https://create.pinterest.com/blog/best-ways-to-grow-on-pinterest/)\n- [Pin specifications](https://help.pinterest.com/en/article/review-pin-specs)\n- [Pin performance and distribution](https://help.pinterest.com/en/business/article/pin-performance-and-distribution)\n- [Pinterest Analytics definitions](https://help.pinterest.com/en/business/article/pinterest-analytics)\n- [Pinterest developer guidelines](https://policy.pinterest.com/en/developer-guidelines)\n- [Pinterest API schema](https://github.com/pinterest/api-description/blob/main/v5/openapi.json)\n- [Pinterest community guidelines](https://policy.pinterest.com/en/community-guidelines)\n- [Pinterest labels for AI-generated or modified images](https://help.pinterest.com/en/article/gen-ai-labels)\n- [Claim your website](https://help.pinterest.com/en/business/article/claim-your-website)\n- [Rich Pins](https://help.pinterest.com/en-gb/business/article/rich-pins)\n",
1164
+ "revision": "563dbb5a9c4b3053"
1165
+ },
1166
+ {
1167
+ "id": "distribution",
1168
+ "title": "Why a post travels",
1169
+ "summary": "Explains what Instagram and TikTok actually reward, and turns each mechanic into a rule you can build a post against. Read it before deciding a format, a slide count or a caption length, and when a post that looked good got no reach.",
1170
+ "url": "https://sprid.studio/docs/distribution",
1171
+ "markdown": "# Why a post travels\n\n**What this guide does:** Explains what Instagram and TikTok actually reward,\nand turns each mechanic into a rule you can build a post against. Read it before\ndeciding a format, a slide count or a caption length, and when a post that\nlooked good got no reach.\n\nWhether a post is *good* and whether it *travels* are two different questions.\nThis one is about the second. Every rule here exists because of a specific\nmechanic, and if the mechanic changes the rule goes with it - so each is written\nwith its reason attached rather than as a commandment.\n\n**Verified against published sources 2026-08-04.** Platform mechanics decay.\nTreat anything here as a claim about a moving system, re-check it quarterly, and\nprefer your own numbers the moment you have them.\n\n## Instagram\n\n**The three named ranking signals are watch time, sends per reach, and likes per\nreach.** Sends are the heaviest, reported at roughly three to five times the\nweight of a like. Sends per reach is specifically the signal that reaches people\nwho do not follow you, which is the only reach a new account can grow on.\n\n**Feed, Reels, Stories and Explore rank separately** and weight those signals\ndifferently. Feed leans on how close you already are to the viewer; Explore on\nengagement velocity and interest match. A post that does well with your existing\nfollowers is not automatically a post that travels.\n\n**Why carousels, specifically:**\n\n- Every swipe is engagement, and dwell time accumulates across the slides.\n Carousels are reported at two to three times the reach of a single image for\n the same content.\n- **The platform re-serves a carousel to people who did not swipe, starting from\n the second slide.** This is the most actionable mechanic available: slide two\n gets an independent second chance to be someone's first impression.\n- Carousels out-save single images by a wide margin, and saves compound, because\n saved posts get resurfaced.\n- Completion - how many people reach the last slide - is what pushes a post out\n of your followers and into Explore.\n\n**Hashtags do not drive distribution.** The platform's own position is that they\ncategorise rather than distribute. Discovery comes from the words in your\ncaption: captions, alt text, bios and on-screen text are indexed and served into\nin-app search. Keyword-rich captions have been measured at around 30% more reach\nthan hashtag-heavy ones. Keep three to five hashtags as labels and spend the\neffort on the prose.\n\n## TikTok, photo mode\n\n- Ranking is swipe-through rate, dwell time and reverse swipes, with completion\n rate as the primary signal.\n- Distribution starts with a **small test batch of roughly 200-500 viewers**,\n mostly followers and people who engage with adjacent content. What that batch\n does decides everything afterwards. This is why the first hour matters, and\n why a weak second slide is fatal rather than merely costly.\n- Saves are weighted and photo posts save well. A carousel with fewer views but\n a high save-and-comment share is doing better than a higher-view post that\n bounces on slide one.\n- Interest-based distribution means an outlier is possible from your first post.\n TikTok is spikier; Instagram grinds.\n\n## What follows for how you build a post\n\n| Rule | Because |\n|---|---|\n| **Slide 2 is a second hook.** It delivers on slide 1 *and* opens a new thread, and it has to work cold | the platform re-serves from slide 2; TikTok's test batch dies there |\n| **Seven slides for a narrative format** (hook, five body, closer) | seven to ten is the reported dwell-time sweet spot; under five reads as a short post, over ten causes mid-carousel fatigue |\n| **No slide may be skippable.** If a body slide can be removed without breaking the post, you wrote a list, not an experience | completion is the ranking signal, and a list lets people stop anywhere |\n| **The peak lands in the last third** | a middle peak makes the tail a letdown, and the tail is where completion is won |\n| **Design for the send, not the like.** At least one slide should make someone think of a specific person | sends per reach is the non-follower signal, worth several likes |\n| **The send prompt lives in the caption**, naming one kind of person, never \"share if you relate\" | it belongs where sends are earned, and it keeps the last slide screenshottable |\n| **At least two slides hand over something usable** | recognition earns likes; recognition plus something doable earns saves, and saves compound |\n| **Captions carry the audience's own search phrases, in prose** | captions are indexed; hashtags are not distribution |\n| **Caption length 400-600 characters on Instagram, 150-300 on TikTok**, first line an independent hook under 125 characters | that is where the \"more\" cut falls; past roughly 300 characters TikTok needs a tap and reach drops |\n| **Three to five hashtags, never the generic feed tag** | labels, not reach |\n| **One canvas at 4:5, content clear of the top and bottom edges** | survives the 3:4 grid crop, letterboxes acceptably on TikTok |\n\n## Cadence, and the first hour\n\n**Three to five posts a week, sustained.** Three a week for twelve weeks beats\nseven a week for three. The failure mode is not low quality, it is stopping - and\nthe documented version of it is 34 drafts and 3 published posts.\n\n**The first hour is the test batch.** Being there to answer early comments is the\ncheapest intervention available on either platform; a comment answered in the\nfirst hour is worth more than the same reply a day later.\n\n**Post at a fixed time** your audience is awake for, set once on the account's\nposting schedule rather than decided per post.\n\n**Expect silence for two months.** A new account with no face takes three to six\nmonths to reach a thousand followers on Instagram. Distribution is a power law:\na few posts carry most of the reach. Judging before 90 days is judging noise.\n\n## When something works, fan it out\n\nA post that breaks out is the beginning of the work, not the end.\n\n1. Make five to ten variants of the winner with the structure fixed and **exactly\n one variable changed** - the hook phrasing, the register, the opening image.\n2. One variable per variant, or the result teaches nothing.\n3. Log which variant won *and* which source line its hook came from. That tells\n you which well to keep digging.\n4. A losing variant is data. Kill it rather than nursing it.\n\n## What is worth not believing\n\nEverything above is published guidance and platform statements, not our\nmeasurements. Before you treat any of it as settled for **your** audience, these\nare the clean one-variable tests: caption length judged on saves and sends per\nreach; a quiet text card against a photo behind text; a native 9:16 crop against\na letterboxed 4:5; whether a screenshot of your app costs reach or buys installs.\n\nLog the real numbers per post at seven days - views, completion, saves, sends,\ncomments, profile taps. The moment you have your own numbers they outrank every\nsource below.\n\n## Sources\n\n- [Instagram algorithm ranking signals (Buffer)](https://buffer.com/resources/instagram-algorithms/) - watch time, sends per reach, likes per reach, per-surface ranking\n- [The ranking signals that matter (Clixie)](https://www.clixie.ai/blog/instagram-algorithm) - sends weighted three to five times a like\n- [How carousels beat Reels for engagement (Storrito)](https://storrito.com/resources/how-instagram-carousels-beat-reels-for-engagement-in-2026-and-when-to-use-each/) - re-serving from slide 2, reach multiple\n- [Carousel best practices (Adpicto)](https://www.adpicto.com/en/blog/instagram-carousel-best-practices-2026) - slide count, dwell time, saves\n- [Carousel algorithm (TryMyPost)](https://www.trymypost.com/blog/instagram-carousel-algorithm-strategy-2026) - dwell time and completion\n- [Do hashtags still work (Kontentino)](https://www.kontentino.com/q-and-a/instagram-hashtags-reach/) - hashtags do not drive reach\n- [Keywords versus hashtags (Dive Media)](https://www.divemedia.com.au/marketing-tips-and-insights/social-seo-keywords-vs-hashtags) - caption indexing and the reach lift\n- [TikTok photo mode algorithm (ReelBase)](https://reelbase.io/blog/tiktok-photo-mode-algorithm-explained) - photo mode reach against video\n- [TikTok carousel algorithm (PostWaffle)](https://www.postwaffle.com/blog/tiktok-carousel-algorithm) - swipe-through, reverse swipes, test batch size, saves\n",
1172
+ "revision": "6799e74c42287dc5"
1173
+ },
1174
+ {
1175
+ "id": "reel-first-seconds",
1176
+ "title": "The first seconds of a reel",
1177
+ "summary": "Explains why a vertical video is watched or skipped in its opening seconds, and gives the timing arithmetic to build one that gets past it. Read it before writing a hook, choosing a length, or diagnosing a reel that got no views.",
1178
+ "url": "https://sprid.studio/docs/reel-first-seconds",
1179
+ "markdown": "# The first seconds of a reel\n\n**What this guide does:** Explains why a vertical video is watched or skipped in\nits opening seconds, and gives the timing arithmetic to build one that gets past\nit. Read it before writing a hook, choosing a length, or diagnosing a reel that\ngot no views.\n\nBoth platforms decide a video's reach from a small first batch of viewers and\nfrom what each of them does in the first seconds. Nothing after second three\nmatters to someone who left at second two. Everything below follows from that.\n\nEach claim carries how we know it: **measured** (we ran it and wrote the number\ndown), **reproduced** (we made the mistake and watched it happen), **published**\n(the platform or a cited source says so, we have not tested it).\n\n## Frame 0 is the cover, so it must already be readable\n\n**Reproduced, twice, on two different accounts.** The feed shows the video's\nfirst frame as its poster image. A hook that fades in from black has a black\ncover. A hook that animates up from 0.42 opacity reads as washed-out grey for\nthe first half second - which is inside the window that decides whether anyone\nstays.\n\nPlace the first card. Do not animate it in. Light it at 0.7 opacity or more, and\nmake it true at t=0.\n\n## The opener has to end before second three\n\n**Reproduced.** A hook card held for 3.3 seconds puts the first cut at 3.1\nseconds, so the entire skip window contains one motionless card. Nothing has\nhappened yet when the viewer decides.\n\nSize the opener to its own reading load with a floor of 1.6 seconds, and check\nthat the first cut lands under three. A cut inside the window is the cheapest\nsignal you have that this video is going somewhere.\n\n## Length: one measured result, and its confound\n\n**Measured, on one of our own accounts, 2026-08-29.** A batch cut to 15.5\nseconds drew 113-162 views on an account with a single follower. The 29-32\nsecond cuts that followed on the same account drew 6-22, and one sat at 6 views\nafter 29 hours - a distribution collapse rather than a slow start.\n\nState the confound honestly: three things changed in that batch at once\n(background, word density, length). Length is the reversible one, not the proven\none. That account now runs a 10-13 second test. **Neither band is a benchmark**,\nand anyone quoting 15.5 seconds as an optimum - including us - is over-reading a\nsingle comparison.\n\n## The timing arithmetic\n\nReading load, not taste. These are the holds that stop a card from reading faster\nthan it is shown.\n\n| | hold | why |\n|---|---|---|\n| any word | 300 ms | silent reading speed on a phone |\n| any character | 52 ms | a compound noun is one word and twenty letters, so take `max(words, chars)` |\n| opener | 1.6-2.8 s | reading load + 0.5 s, and the first cut must land under 3 s |\n| body or turn | 2.4-4.2 s | reading load + 0.7 s |\n| a big number | 2.6-3.8 s | the number reads at a glance, its label does not |\n| closer | 3.6-5.0 s | reading load + 1.3 s; this is the card people screenshot |\n| overlap | 200 ms | each card starts before the previous one leaves |\n\nWord budgets per card, never totalled: hook 3-11, body 3-14, closer 4-16. A\n3.5-second card holds roughly 10 words or 55 characters at a comfortable size.\nPast that the card reads faster than it holds, and the viewer waits - which is\nthe same experience as being bored.\n\n## What a hook is, and is not\n\n- A sentence your buyer has actually thought, or the sharpest fact stated flat.\n A scene or a number. It opens a loop the closer shuts.\n- **Not a \"how to\".** That creates an expectation of information, and information\n can be postponed. Not a question. Not \"did you know\".\n- **Recognition stops a scroll; tempo does not.** A harvested ten-word hook beat\n an invented four-word one on our own account (**reproduced**). Hooks come from\n what people actually said - forum threads, reviews, support mail - not from the\n bank of phrases that sound like hooks.\n- **The second card is a second hook.** It has to work cold, with the first card\n gone. On carousels the platform literally re-serves from the second slide; on\n video, the first cut is the same second chance.\n- The closer is flat and screenshottable, and it lets the product do the work\n rather than issuing an install instruction.\n\n## The kill rule\n\n**Published**, from platform averages rather than our own numbers: under 25% of\nviewers still watching past three seconds, keep the body and replace the hook.\nAround 30% is healthy and 40% is exceptional on TikTok.\n\nTwo guards on that rule. Never judge a format before ten posts, and never judge\na new account before 90 days - distribution is a power law, and a handful of\nposts carry most of the reach. Reading the first three posts of a new account\nis reading noise.\n\n## Where that number actually lives\n\nThis matters more than it sounds, because the metric the kill rule needs is the\none hardest to get:\n\n- Views, likes, comments, shares, saves and reach come back through the\n platforms' public APIs, and Sprid stores them per post.\n- **Three-second hook rate and the retention curve exist only inside the native\n Instagram and TikTok insights screens.** There is no API field for them as an\n organic number. If you need them, you open the app.\n- Average watch time is available through both business APIs, which makes\n `average watch ÷ duration` the honest proxy you can automate.\n- Taps through to your site or store are the number that pays. The rest are\n directional.\n\n## One picture, many languages\n\nThe reason a demo video is usually made once is that the work is a person\nholding a phone. Split it instead: the wordless part (the footage, the gesture\nscript, the photographs) and the worded part (the captions and cards, bound to\nmarks in the footage rather than to timestamps).\n\nThe rule that makes it hold: **no word is ever inside a picture.** A wordless\ncutaway serves every language you have a string table for, so a seventh language\ncosts its translation and one render rather than a second shoot.\n",
1180
+ "revision": "2997f4c1f66e439a"
1181
+ },
1182
+ {
1183
+ "id": "store-listing",
1184
+ "title": "Write a store listing people can find",
1185
+ "summary": "Explains what each field of an App Store and Google Play listing is actually for, which characters are wasted, and what to change first. Read it before editing a listing, and before accepting any keyword suggestion.",
1186
+ "url": "https://sprid.studio/docs/store-listing",
1187
+ "markdown": "# Write a store listing people can find\n\n**What this guide does:** Explains what each field of an App Store and Google\nPlay listing is actually for, which characters are wasted, and what to change\nfirst. Read it before editing a listing, and before accepting any keyword\nsuggestion.\n\nA listing is two jobs in one form. Some fields decide whether you appear in a\nsearch at all; others decide whether the person who found you taps Get. They\nare not the same words, and writing all of them for one job is the most common\nway a listing quietly costs downloads.\n\n## The fields, and what each one does\n\n**App Store name, 30 characters. Indexed.** Your brand, and - when the evidence\nsupports it - one term describing the category. It is the heaviest indexed\nfield, so a name of only a coined brand word spends the store's strongest signal\non a word nobody searches. Against that: an established name is an asset you\nalready own. Quote the current name and the alternatives side by side and decide\nin the open, rather than letting a keyword tool pick.\n\n**App Store subtitle, 30 characters. Indexed.** The benefit, the use case, or the\none thing that separates you from the app above you in the results. It is read\nby a person mid-scroll and indexed by the store, which is why it is the hardest\n30 characters in the listing.\n\n**App Store keyword field, 100 characters. Indexed, invisible.** Comma-separated,\n**no spaces after the commas** - a space costs a character and buys nothing. The\nrules that reclaim the most room:\n\n- **No word that already appears in your name or subtitle.** Those are indexed\n already, and repeating them is the single biggest waste of the 100.\n- **No plural beside its own singular.** The store handles that.\n- No category names the store already knows you by, and no competitor brands.\n- Single words, not phrases: the store builds combinations across your terms, so\n `budget,tracker,expense` covers more searches than `budget tracker`.\n\n**Google Play has no keyword field, and that changes the work.** Play indexes the\ntitle (30), the short description (80) and the full description (4000). So on\nPlay the searchable words have to live inside sentences a human also reads -\nnaturally, a few times, not stuffed. The same app therefore needs two different\npieces of writing, and a listing that pastes the Apple keyword string into a Play\ndescription reads like spam to both the algorithm and the reader.\n\n**The first line of the description survives the fold** (around 170 characters on\niOS before \"more\"). Almost nobody opens the rest - but the store's reviewers do,\nso every claim in it has to be true.\n\n## What to change first\n\nIn order, because this is the order of what it costs to be wrong:\n\n1. **The subtitle**, if it is a slogan. A slogan is indexed and converts nothing.\n2. **The keyword field**, if it repeats the name, carries plurals or has spaces\n after commas. This is pure reclaimed space and needs no new idea.\n3. **The first two lines of the description**, if they open with the company\n rather than with what the person came for.\n4. **The name**, last and rarely. It is the one change that costs you the\n recognition you have already built, and it resets any word-of-mouth search.\n\n## Locales are separate listings, not translations\n\nChoose the locales your actual markets use, and write each one's keyword field\nagainst that language's searches rather than translating the English terms. The\nuseful words are frequently not the translated ones: people search the phrase\nthey say out loud, and in some languages that is a compound noun with no English\nshape at all. Keep what already works in a locale - an existing keyword that\nearns installs is evidence, and replacing it with a tidier translation is how a\nrewrite loses ground.\n\nDo not assume one English locale covers every English-speaking country. Check\nthe store's current localization guidance before making any claim about which\nlocale is indexed where; it changes, and a confident wrong answer here costs a\nwhole market's discoverability.\n\n## When the store will and will not accept a change\n\nKeywords and the description are attached to a **version** on App Store Connect.\nThey push only when an editable version exists - one in \"Prepare for Submission\" -\nand the name and subtitle additionally need an editable app information record.\nSo a listing change is not a live edit: it is a change that ships with your next\nrelease, and a dry run that validates your text locally can still be refused by\nthe store minutes later for this reason alone.\n\nSprid's listing tooling prints the exact fields and values it would send before\nit sends anything, and pushes only on an explicit execute. Read the diff. A\nlisting is one of the few things in marketing where a bad change is visible to\nevery future visitor and takes a release to undo.\n\n## How to tell whether it worked\n\nImpressions, not installs, are the field that moves when a listing's indexed\nwords get better; the conversion rate is what moves when the subtitle and first\nscreenshot get better. Separating those two is the whole reason to change one at\na time.\n\nGive it a full release cycle plus two weeks before reading the result, and\ncompare against the same weekday span, not against the launch spike. If the app\nalso runs paid installs, exclude that traffic first or you will read the ad\nbudget as a listing improvement.\n",
1188
+ "revision": "404de4aaddcf2979"
1177
1189
  }
1178
1190
  ];