@sprid/cli 0.1.5 → 0.1.7
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.
- package/CHANGELOG.md +24 -0
- package/README.md +13 -2
- package/bin.mjs +1 -1
- package/package.json +2 -2
- package/src/args.mjs +22 -1
- package/src/cli.mjs +27 -5
- package/src/clipboard.mjs +75 -0
- package/src/commands/ads.mjs +110 -0
- package/src/commands/connect.mjs +99 -26
- package/src/commands/events.mjs +60 -0
- package/src/commands/inbox.mjs +124 -0
- package/src/commands/post.mjs +2 -0
- package/src/commands/publishing.mjs +109 -0
- package/src/commands/status.mjs +9 -1
- package/src/commands/tool.mjs +59 -0
- package/src/docs/commands.mjs +33 -0
- package/src/docs/guides.generated.mjs +299 -52
- package/src/docs/help.mjs +1 -0
- package/src/telemetry.mjs +140 -0
- package/src/tools.mjs +85 -0
|
@@ -29,12 +29,21 @@ export const GUIDE_GROUPS = [
|
|
|
29
29
|
"slugs": [
|
|
30
30
|
"instagram",
|
|
31
31
|
"tiktok",
|
|
32
|
+
"tiktok-comments",
|
|
32
33
|
"youtube",
|
|
33
34
|
"linkedin",
|
|
34
35
|
"facebook",
|
|
35
36
|
"x",
|
|
36
37
|
"pinterest"
|
|
37
38
|
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"title": "Ads",
|
|
42
|
+
"slugs": [
|
|
43
|
+
"meta-ads",
|
|
44
|
+
"google-ads",
|
|
45
|
+
"tiktok-ads"
|
|
46
|
+
]
|
|
38
47
|
}
|
|
39
48
|
];
|
|
40
49
|
export const GUIDE_ALIASES = {
|
|
@@ -43,7 +52,13 @@ export const GUIDE_ALIASES = {
|
|
|
43
52
|
"gsc": "search-console",
|
|
44
53
|
"ga4": "google-analytics",
|
|
45
54
|
"ga": "google-analytics",
|
|
46
|
-
"google-analytics-4": "google-analytics"
|
|
55
|
+
"google-analytics-4": "google-analytics",
|
|
56
|
+
"meta_ads": "meta-ads",
|
|
57
|
+
"meta": "meta-ads",
|
|
58
|
+
"google_ads": "google-ads",
|
|
59
|
+
"tiktok_ads": "tiktok-ads",
|
|
60
|
+
"tiktok_accounts": "tiktok-comments",
|
|
61
|
+
"tiktok-accounts": "tiktok-comments"
|
|
47
62
|
};
|
|
48
63
|
export const METRIC_EVIDENCE = "# Metric evidence\n\nSprid’s review packet includes a versioned `dataset`: observations, checked results and hashes of the source receipts. The CLI saves complete review/query packets under `marketing-reports/evidence/` with immutable content-hash filenames and owner-only file permissions. The plugin runner preserves that dataset alongside its local evidence and git context. An explicit runner `--output` refuses to overwrite an existing file.\n\n## Read a number\n\n- `definition` names the entity, measurement kind, unit, calculation basis and implementation version. An active subscription is not a unique customer; a store download is not an activated person.\n- `window` uses an exclusive end and retains its calendar. `asOf` belongs to a snapshot; `fetchedAt` records collection. Historical revenue and current MRR are separate observations.\n- `quality` separates coverage, pagination, sampling, finality and trust. A successful HTTP request does not prove complete coverage. Unknown, pending, suppressed and unavailable values remain missing.\n- `value.kind: money` stores an exact decimal coefficient and scale with a currency and conversion policy. Do not assume every provider amount is cents. Keep gross, refunded revenue and proceeds separate.\n\n## Respond to a blocked calculation\n\n| Reason | Continue with |\n| --- | --- |\n| `currency_mismatch` | Separate currency rows; convert only with explicit dated FX evidence and a declared policy. |\n| `overlap_unresolved` | Source subtotals and the identity check. Do not add RevenueCat and a rail it already ingests. |\n| `definition_mismatch` | Name the provider definitions separately, including MRR policy and revenue basis. |\n| `window_mismatch` | Request matching periods/calendars. A daily Pacific aggregate cannot be relabeled UTC. |\n| `population_mismatch` | Verify numerator/denominator attribution with an actual identity mapping. All-app revenue cannot stand for website-cohort revenue. |\n| `incomplete_sources`, `incomplete_coverage` | Keep usable readings; complete pagination or narrow the period. Missing dates are not automatically zero. |\n| `unverified_calculation` | Inspect emitting code and the query’s population, ordering and exclusions. Preserve a local attestation for local database work. |\n| `non_additive` | Query period-level distinct people, select the relevant snapshot, or recompute a rate from compatible numerators and denominators. |\n\n## Investigate through Sprid\n\nRun `sprid marketing-review capabilities --app <slug>` to discover operations. The `revenuecat` / `revenue` operation reads an explicit period total, with `revenue_type` set to `revenue`, `revenue_net_of_taxes` or `proceeds`. Its scope is the bound RevenueCat project, which can contain multiple apps. Use `chart_options` before selecting chart dimensions. Cohort-chart period zero may describe customer count, not the month-zero metric.\n\nCustom query results carry `measurement` provenance and retain provider-native data. They remain investigative; the server does not certify arbitrary SQL’s business meaning. The shared calculation library supports sequential/strict ordered funnels and fixed elapsed-time retention from complete event evidence. These helpers are not additional public query operation names. For provider-side investigations, use the discovered custom query operation and preserve the exact SQL.\n\nStripe MRR is a current active/past-due price estimate before discounts. Paddle’s adapter reports gross completed transactions before adjustments. Lemon Squeezy combines orders with renewal/update invoices and excludes duplicate initial invoices; a complete priced schedule is required for MRR. Separate source readings survive when combined totals cannot be justified.\n\nRevenue observations and aggregate Search Console observations are normalized in the review dataset. Other source receipts retain native definitions and coverage; they are not silently converted into a universal business scorecard. App-specific activation, cross-source identity and attribution still require repository evidence. Local fallback results do not automatically inherit the server dataset’s verification.\n\nSave the full packet and exact query beside the report. Hashes identify evidence but cannot recreate it. Link corrected reports to their earlier dataset and retain the earlier packet; provider backfills and refund revisions must remain inspectable. Sprid does not upload repository database rows or provide hosted dataset-history storage through this contract.\n";
|
|
49
64
|
export const GUIDES = [
|
|
@@ -299,7 +314,7 @@ export const GUIDES = [
|
|
|
299
314
|
"id": "posthog",
|
|
300
315
|
"title": "PostHog",
|
|
301
316
|
"summary": "See how people use your app and where they stop.",
|
|
302
|
-
"command": "sprid connect posthog --app myapp --key
|
|
317
|
+
"command": "sprid connect posthog --app myapp --key-from-clipboard --project 12345 --host eu",
|
|
303
318
|
"url": "https://sprid.studio/docs/connect/posthog",
|
|
304
319
|
"sections": [
|
|
305
320
|
{
|
|
@@ -312,7 +327,7 @@ export const GUIDES = [
|
|
|
312
327
|
"id": "click-path-usposthogcom-or-euposthogcom",
|
|
313
328
|
"title": "Click path (us.posthog.com or eu.posthog.com)",
|
|
314
329
|
"kind": "steps",
|
|
315
|
-
"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.
|
|
330
|
+
"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. Create the key and keep the dialog open: PostHog shows it once. Use a separate key per app.\n5. Copy the key and leave it on your clipboard."
|
|
316
331
|
},
|
|
317
332
|
{
|
|
318
333
|
"id": "project-id-and-host",
|
|
@@ -324,7 +339,7 @@ export const GUIDES = [
|
|
|
324
339
|
"id": "then-run",
|
|
325
340
|
"title": "Then run",
|
|
326
341
|
"kind": "command",
|
|
327
|
-
"markdown": "```sh\nsprid connect posthog --app myapp --key
|
|
342
|
+
"markdown": "```sh\nsprid connect posthog --app myapp --key-from-clipboard --project 12345 --host eu\n```\n\nUse your own app slug, project id and host. Add `--workspace <slug>` if needed. Keep the key out of chat.\n\nSprid reads the key from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the key is copied and it runs this for you. Typing it yourself, copy the key last: paste the command into your terminal first, then copy the key, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the key and run it again. Without clipboard access (a remote shell), save the key to a file and pass `--key <file>` instead."
|
|
328
343
|
},
|
|
329
344
|
{
|
|
330
345
|
"id": "how-to-check-it-worked",
|
|
@@ -369,8 +384,8 @@ export const GUIDES = [
|
|
|
369
384
|
"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)"
|
|
370
385
|
}
|
|
371
386
|
],
|
|
372
|
-
"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 product users | First product activity, including anonymous people |\n| Active product users | Distinct people with product activity in the period |\n| Web visitors | Distinct pageview visitors on the saved website hostname, minus your product |\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### What counts as your product\n\nSet this under **What counts as your product** on the same screen. It decides who is an active person, and it is the single setting most likely to make the people card wrong.\n\n| Your product is | Choose | Also give |\n|---|---|---|\n| An iOS or Android app | **A phone app** | Nothing. This is the default |\n| A web app on its own subdomain | **A website or web app** | The hostnames, such as `app.example.com` |\n| A web app under a path on your marketing domain | **A website or web app** | The paths, such as `/app`, `/dashboard` |\n| A phone app with a web client | **Both** | The web hostnames or paths |\n\n**A web product must say where it lives.** Your marketing pages and your product are the same PostHog events; without a hostname or a path there is nothing to tell them apart, and every visitor would be counted as someone using the product. Sprid refuses that configuration rather than reporting it.\n\nWhatever you name here is **subtracted from your website visitor numbers**, so a page inside the product is never also counted as a visit.\n\n**A phone app** needs `posthog-react-native` on iOS, iPadOS or Android and rejects 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\":{\"kind\":\"web\",\"hosts\":[\"app.example.com\"]},\"exclude\":[{\"scope\":\"person\",\"property\":\"staff\",\"operator\":\"in\",\"values\":[true]}]}\n```\n\n- `app.kind` is `native`, `web` or `both`, with `app.hosts` and `app.pathPrefixes` saying where a web product lives. This is what the screen above writes.\n- `app.filters` replaces the whole definition with your own rule; all filters must match. Choosing it shows as **Custom rules** on the screen, and the rule is subtracted from web visitors the same way.\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",
|
|
373
|
-
"revision": "
|
|
387
|
+
"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. Create the key and keep the dialog open: PostHog shows it once. Use a separate key per app.\n5. Copy the key and leave it on your clipboard.\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-from-clipboard --project 12345 --host eu\n```\n\nUse your own app slug, project id and host. Add `--workspace <slug>` if needed. Keep the key out of chat.\n\nSprid reads the key from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the key is copied and it runs this for you. Typing it yourself, copy the key last: paste the command into your terminal first, then copy the key, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the key and run it again. Without clipboard access (a remote shell), save the key to a file and pass `--key <file>` instead.\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 product users | First product activity, including anonymous people |\n| Active product users | Distinct people with product activity in the period |\n| Web visitors | Distinct pageview visitors on the saved website hostname, minus your product |\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### What counts as your product\n\nSet this under **What counts as your product** on the same screen. It decides who is an active person, and it is the single setting most likely to make the people card wrong.\n\n| Your product is | Choose | Also give |\n|---|---|---|\n| An iOS or Android app | **A phone app** | Nothing. This is the default |\n| A web app on its own subdomain | **A website or web app** | The hostnames, such as `app.example.com` |\n| A web app under a path on your marketing domain | **A website or web app** | The paths, such as `/app`, `/dashboard` |\n| A phone app with a web client | **Both** | The web hostnames or paths |\n\n**A web product must say where it lives.** Your marketing pages and your product are the same PostHog events; without a hostname or a path there is nothing to tell them apart, and every visitor would be counted as someone using the product. Sprid refuses that configuration rather than reporting it.\n\nWhatever you name here is **subtracted from your website visitor numbers**, so a page inside the product is never also counted as a visit.\n\n**A phone app** needs `posthog-react-native` on iOS, iPadOS or Android and rejects 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\":{\"kind\":\"web\",\"hosts\":[\"app.example.com\"]},\"exclude\":[{\"scope\":\"person\",\"property\":\"staff\",\"operator\":\"in\",\"values\":[true]}]}\n```\n\n- `app.kind` is `native`, `web` or `both`, with `app.hosts` and `app.pathPrefixes` saying where a web product lives. This is what the screen above writes.\n- `app.filters` replaces the whole definition with your own rule; all filters must match. Choosing it shows as **Custom rules** on the screen, and the rule is subtracted from web visitors the same way.\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",
|
|
388
|
+
"revision": "c23bf372172c52f7"
|
|
374
389
|
},
|
|
375
390
|
{
|
|
376
391
|
"id": "google-analytics",
|
|
@@ -435,7 +450,7 @@ export const GUIDES = [
|
|
|
435
450
|
"id": "plausible",
|
|
436
451
|
"title": "Plausible",
|
|
437
452
|
"summary": "See how many people visit your website, where they came from and which pages they land on.",
|
|
438
|
-
"command": "sprid connect plausible --key
|
|
453
|
+
"command": "sprid connect plausible --key-from-clipboard --site example.com",
|
|
439
454
|
"url": "https://sprid.studio/docs/connect/plausible",
|
|
440
455
|
"sections": [
|
|
441
456
|
{
|
|
@@ -448,19 +463,19 @@ export const GUIDES = [
|
|
|
448
463
|
"id": "click-path",
|
|
449
464
|
"title": "Click path",
|
|
450
465
|
"kind": "steps",
|
|
451
|
-
"markdown": "1. Open [Plausible](https://plausible.io) and switch to the team that owns the site.\n2. **Settings → API keys → New API key**.\n3. Name it `sprid
|
|
466
|
+
"markdown": "1. Open [Plausible](https://plausible.io) and switch to the team that owns the site.\n2. **Settings → API keys → New API key**.\n3. Name it `sprid` and create it. Plausible shows it once.\n4. Copy the key and leave it on your clipboard.\n5. Your **site id** is the domain exactly as it was added to Plausible: no `https://`, no trailing slash. It is the last part of the site’s URL, `plausible.io/<site id>`."
|
|
452
467
|
},
|
|
453
468
|
{
|
|
454
469
|
"id": "then-run",
|
|
455
470
|
"title": "Then run",
|
|
456
471
|
"kind": "command",
|
|
457
|
-
"markdown": "```\nsprid connect plausible --key
|
|
472
|
+
"markdown": "```\nsprid connect plausible --key-from-clipboard --site example.com\n```\n\nSelf-hosting? Add `--host https://analytics.example.com`. Add `--use` to make Plausible the source Sprid reports website traffic from."
|
|
458
473
|
},
|
|
459
474
|
{
|
|
460
475
|
"id": "only-one-provider-answers",
|
|
461
476
|
"title": "Only one provider answers",
|
|
462
477
|
"kind": "detail",
|
|
463
|
-
"markdown": "PostHog, Google Analytics and Plausible all count the same visits to the same site. Sprid reads **one** of them per app and never adds them together, because adding them would overstate your traffic by roughly the overlap, and the overlap is nearly everything.\n\nWith one connected, that one answers. With several, Sprid uses the one you chose with `--use` (PostHog by default, because it also answers the people and registration cards). The others stay connected and idle, and the traffic card says so."
|
|
478
|
+
"markdown": "PostHog, Google Analytics and Plausible all count the same visits to the same site. Sprid reads **one** of them per app and never adds them together, because adding them would overstate your traffic by roughly the overlap, and the overlap is nearly everything.\n\nWith one connected, that one answers. With several, Sprid uses the one you chose with `--use` (PostHog by default, because it also answers the people and registration cards). The others stay connected and idle, and the traffic card says so.\n\nSprid reads the key from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the key is copied and it runs this for you. Typing it yourself, copy the key last: paste the command into your terminal first, then copy the key, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the key and run it again. Without clipboard access (a remote shell), save the key to a file and pass `--key <file>` instead."
|
|
464
479
|
},
|
|
465
480
|
{
|
|
466
481
|
"id": "how-to-check-it-worked",
|
|
@@ -487,14 +502,14 @@ export const GUIDES = [
|
|
|
487
502
|
"markdown": "- [Stats API v2 `query`, and the Business-plan requirement](https://plausible.io/docs/stats-api)"
|
|
488
503
|
}
|
|
489
504
|
],
|
|
490
|
-
"markdown": "# Plausible\n\n**What Sprid does with this:** See how many people visit your website, where they came from and which pages they land on.\n\n## You need\n\nA **Business** plan or higher. The Stats API is a Business-plan feature, so a valid key on a cheaper plan is refused with a plan error rather than a key error. An API key also belongs to **one team**, so create it in the team that owns the site.\n\n## Click path\n\n1. Open [Plausible](https://plausible.io) and switch to the team that owns the site.\n2. **Settings → API keys → New API key**.\n3. Name it `sprid
|
|
491
|
-
"revision": "
|
|
505
|
+
"markdown": "# Plausible\n\n**What Sprid does with this:** See how many people visit your website, where they came from and which pages they land on.\n\n## You need\n\nA **Business** plan or higher. The Stats API is a Business-plan feature, so a valid key on a cheaper plan is refused with a plan error rather than a key error. An API key also belongs to **one team**, so create it in the team that owns the site.\n\n## Click path\n\n1. Open [Plausible](https://plausible.io) and switch to the team that owns the site.\n2. **Settings → API keys → New API key**.\n3. Name it `sprid` and create it. Plausible shows it once.\n4. Copy the key and leave it on your clipboard.\n5. Your **site id** is the domain exactly as it was added to Plausible: no `https://`, no trailing slash. It is the last part of the site’s URL, `plausible.io/<site id>`.\n\n## Then run\n\n```\nsprid connect plausible --key-from-clipboard --site example.com\n```\n\nSelf-hosting? Add `--host https://analytics.example.com`. Add `--use` to make Plausible the source Sprid reports website traffic from.\n\n## Only one provider answers\n\nPostHog, Google Analytics and Plausible all count the same visits to the same site. Sprid reads **one** of them per app and never adds them together, because adding them would overstate your traffic by roughly the overlap, and the overlap is nearly everything.\n\nWith one connected, that one answers. With several, Sprid uses the one you chose with `--use` (PostHog by default, because it also answers the people and registration cards). The others stay connected and idle, and the traffic card says so.\n\nSprid reads the key from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the key is copied and it runs this for you. Typing it yourself, copy the key last: paste the command into your terminal first, then copy the key, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the key and run it again. Without clipboard access (a remote shell), save the key to a file and pass `--key <file>` instead.\n\n## How to check it worked\n\nRun `sprid status`, then ask your agent: “Read this website’s visitors for the last 28 days through Sprid and say which provider answered.” A saved key confirms setup; only the live read confirms access.\n\n## If it fails\n\n- **Business plan required:** the key is fine; the plan does not include the Stats API. Move to Business, or use PostHog or Google Analytics for this app instead. Nothing needs re-entering afterwards.\n- **Unauthorized:** the key belongs to a different team from the site. Create the key inside the team that owns the site.\n- **Site not found:** the site id is the domain as Plausible has it, with no scheme and no trailing slash.\n- **Rate limited:** Plausible allows 600 requests an hour per key. One reading is well inside that, so check whether another tool shares the key.\n\n## What Sprid can and cannot read here\n\nVisitors, pageviews, visits, bounce rate and visit duration, plus breakdowns by country, region, city, source, channel, campaign, term, page, entry page, exit page, hostname, browser, operating system and device.\n\nPlausible is **cookieless**: it has no person id, so a visitor is its own daily estimate rather than someone Sprid can follow between days. Window totals therefore come from Plausible directly and are never the daily numbers added up. Bounces and session time arrive as a rate and an average, and Sprid turns them back into counts, so they carry Plausible’s rounding. Days are the site’s own time zone, not UTC.\n\n## Sources\n\n- [Stats API v2 `query`, and the Business-plan requirement](https://plausible.io/docs/stats-api)\n",
|
|
506
|
+
"revision": "5a32f8c4a59285a9"
|
|
492
507
|
},
|
|
493
508
|
{
|
|
494
509
|
"id": "umami",
|
|
495
510
|
"title": "Umami",
|
|
496
511
|
"summary": "See how many people visit your website, where they came from and which pages they land on.",
|
|
497
|
-
"command": "sprid connect umami --key
|
|
512
|
+
"command": "sprid connect umami --key-from-clipboard --site 8f2a1c90-4d1e-4b7a-9f33-2c0b5e7a1d44",
|
|
498
513
|
"url": "https://sprid.studio/docs/connect/umami",
|
|
499
514
|
"sections": [
|
|
500
515
|
{
|
|
@@ -507,19 +522,19 @@ export const GUIDES = [
|
|
|
507
522
|
"id": "click-path",
|
|
508
523
|
"title": "Click path",
|
|
509
524
|
"kind": "steps",
|
|
510
|
-
"markdown": "1. Open [Umami](https://cloud.umami.is), or your own instance.\n2. **Settings → API keys → Create API key**. Name it `sprid
|
|
525
|
+
"markdown": "1. Open [Umami](https://cloud.umami.is), or your own instance.\n2. **Settings → API keys → Create API key**. Name it `sprid`. Umami shows it once.\n3. Copy the key and leave it on your clipboard.\n4. **Settings → Websites →** the site **→ Details**. Copy the **Website ID**. It is a uuid such as `8f2a1c90-4d1e-4b7a-9f33-2c0b5e7a1d44`, **not** the domain."
|
|
511
526
|
},
|
|
512
527
|
{
|
|
513
528
|
"id": "then-run",
|
|
514
529
|
"title": "Then run",
|
|
515
530
|
"kind": "command",
|
|
516
|
-
"markdown": "```\nsprid connect umami --key
|
|
531
|
+
"markdown": "```\nsprid connect umami --key-from-clipboard --site 8f2a1c90-4d1e-4b7a-9f33-2c0b5e7a1d44\n```\n\nSelf-hosting? Add `--host https://analytics.example.com`. Sprid appends the `/api` your instance serves under, so either form works. Add `--use` to make Umami the source Sprid reports website traffic from."
|
|
517
532
|
},
|
|
518
533
|
{
|
|
519
534
|
"id": "only-one-provider-answers",
|
|
520
535
|
"title": "Only one provider answers",
|
|
521
536
|
"kind": "detail",
|
|
522
|
-
"markdown": "PostHog, Google Analytics, Plausible and Umami all count the same visits to the same site. Sprid reads **one** of them per app and never adds them together, because adding them would overstate your traffic by roughly the overlap, and the overlap is nearly everything.\n\nWith one connected, that one answers. With several, Sprid uses the one you chose with `--use` (PostHog by default, because it also answers the people and registration cards). The others stay connected and idle, and the traffic card says so."
|
|
537
|
+
"markdown": "PostHog, Google Analytics, Plausible and Umami all count the same visits to the same site. Sprid reads **one** of them per app and never adds them together, because adding them would overstate your traffic by roughly the overlap, and the overlap is nearly everything.\n\nWith one connected, that one answers. With several, Sprid uses the one you chose with `--use` (PostHog by default, because it also answers the people and registration cards). The others stay connected and idle, and the traffic card says so.\n\nSprid reads the key from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the key is copied and it runs this for you. Typing it yourself, copy the key last: paste the command into your terminal first, then copy the key, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the key and run it again. Without clipboard access (a remote shell), save the key to a file and pass `--key <file>` instead."
|
|
523
538
|
},
|
|
524
539
|
{
|
|
525
540
|
"id": "how-to-check-it-worked",
|
|
@@ -540,14 +555,14 @@ export const GUIDES = [
|
|
|
540
555
|
"markdown": "Visitors, pageviews, visits, bounces and total time, plus **every** breakdown this product draws: country, region, city, referrer, channel, campaign, term, page, entry page, exit page, hostname, browser, operating system and device. Umami is the only alternative provider that reports an exit page.\n\nUmami is **cookieless**, so window totals come from Umami directly and are never the daily numbers added up. The daily line counts **sessions** rather than distinct people, so it will not sum to the visitor total beside it."
|
|
541
556
|
}
|
|
542
557
|
],
|
|
543
|
-
"markdown": "# Umami\n\n**What Sprid does with this:** See how many people visit your website, where they came from and which pages they land on.\n\nUmami is the one to reach for if Plausible's Business plan is more than you want to pay for an API, or if you self-host.\n\n## You need\n\nAn **API key**. On Umami Cloud that is any plan. Self-hosting, you need a version new enough to have **Settings → API keys**; an older instance cannot be connected at all, whatever key you paste.\n\n## Click path\n\n1. Open [Umami](https://cloud.umami.is), or your own instance.\n2. **Settings → API keys → Create API key**. Name it `sprid
|
|
544
|
-
"revision": "
|
|
558
|
+
"markdown": "# Umami\n\n**What Sprid does with this:** See how many people visit your website, where they came from and which pages they land on.\n\nUmami is the one to reach for if Plausible's Business plan is more than you want to pay for an API, or if you self-host.\n\n## You need\n\nAn **API key**. On Umami Cloud that is any plan. Self-hosting, you need a version new enough to have **Settings → API keys**; an older instance cannot be connected at all, whatever key you paste.\n\n## Click path\n\n1. Open [Umami](https://cloud.umami.is), or your own instance.\n2. **Settings → API keys → Create API key**. Name it `sprid`. Umami shows it once.\n3. Copy the key and leave it on your clipboard.\n4. **Settings → Websites →** the site **→ Details**. Copy the **Website ID**. It is a uuid such as `8f2a1c90-4d1e-4b7a-9f33-2c0b5e7a1d44`, **not** the domain.\n\n## Then run\n\n```\nsprid connect umami --key-from-clipboard --site 8f2a1c90-4d1e-4b7a-9f33-2c0b5e7a1d44\n```\n\nSelf-hosting? Add `--host https://analytics.example.com`. Sprid appends the `/api` your instance serves under, so either form works. Add `--use` to make Umami the source Sprid reports website traffic from.\n\n## Only one provider answers\n\nPostHog, Google Analytics, Plausible and Umami all count the same visits to the same site. Sprid reads **one** of them per app and never adds them together, because adding them would overstate your traffic by roughly the overlap, and the overlap is nearly everything.\n\nWith one connected, that one answers. With several, Sprid uses the one you chose with `--use` (PostHog by default, because it also answers the people and registration cards). The others stay connected and idle, and the traffic card says so.\n\nSprid reads the key from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the key is copied and it runs this for you. Typing it yourself, copy the key last: paste the command into your terminal first, then copy the key, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the key and run it again. Without clipboard access (a remote shell), save the key to a file and pass `--key <file>` instead.\n\n## How to check it worked\n\nRun `sprid status`, then ask your agent: “Read this website’s visitors for the last 28 days through Sprid and say which provider answered.” A saved key confirms setup; only the live read confirms access.\n\n## If it fails\n\n- **Unauthorized:** the key cannot see this website, or your self-hosted Umami predates API keys. Check for the **Settings → API keys** screen; if there is none, upgrade first.\n- **Not found:** you probably saved the domain instead of the website id, or left `/api` off a self-hosted host. Sprid adds `/api` for you when you pass `--host`.\n- **Rate limited:** Umami Cloud is limiting requests. The next hourly read picks it up.\n\n## What Sprid can and cannot read here\n\nVisitors, pageviews, visits, bounces and total time, plus **every** breakdown this product draws: country, region, city, referrer, channel, campaign, term, page, entry page, exit page, hostname, browser, operating system and device. Umami is the only alternative provider that reports an exit page.\n\nUmami is **cookieless**, so window totals come from Umami directly and are never the daily numbers added up. The daily line counts **sessions** rather than distinct people, so it will not sum to the visitor total beside it.\n",
|
|
559
|
+
"revision": "12921506ab4331b9"
|
|
545
560
|
},
|
|
546
561
|
{
|
|
547
562
|
"id": "revenuecat",
|
|
548
563
|
"title": "RevenueCat",
|
|
549
564
|
"summary": "See subscription revenue and how it changes over time.",
|
|
550
|
-
"command": "sprid connect revenuecat --app myapp --key
|
|
565
|
+
"command": "sprid connect revenuecat --app myapp --key-from-clipboard --project proj1ab2c3d4",
|
|
551
566
|
"url": "https://sprid.studio/docs/connect/revenuecat",
|
|
552
567
|
"sections": [
|
|
553
568
|
{
|
|
@@ -560,7 +575,7 @@ export const GUIDES = [
|
|
|
560
575
|
"id": "click-path-apprevenuecatcom",
|
|
561
576
|
"title": "Click path (app.revenuecat.com)",
|
|
562
577
|
"kind": "steps",
|
|
563
|
-
"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
|
|
578
|
+
"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**. Use a separate key per app.\n6. Copy the key and leave it on your clipboard."
|
|
564
579
|
},
|
|
565
580
|
{
|
|
566
581
|
"id": "project-id",
|
|
@@ -572,7 +587,7 @@ export const GUIDES = [
|
|
|
572
587
|
"id": "then-run",
|
|
573
588
|
"title": "Then run",
|
|
574
589
|
"kind": "command",
|
|
575
|
-
"markdown": "```sh\nsprid connect revenuecat --app myapp --key
|
|
590
|
+
"markdown": "```sh\nsprid connect revenuecat --app myapp --key-from-clipboard --project proj1ab2c3d4\n```\n\nUse your own app slug and project id. Add `--workspace <slug>` if needed. Keep the key out of chat.\n\nSprid reads the key from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the key is copied and it runs this for you. Typing it yourself, copy the key last: paste the command into your terminal first, then copy the key, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the key and run it again. Without clipboard access (a remote shell), save the key to a file and pass `--key <file>` instead."
|
|
576
591
|
},
|
|
577
592
|
{
|
|
578
593
|
"id": "how-to-check-it-worked",
|
|
@@ -605,14 +620,14 @@ export const GUIDES = [
|
|
|
605
620
|
"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)"
|
|
606
621
|
}
|
|
607
622
|
],
|
|
608
|
-
"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
|
|
609
|
-
"revision": "
|
|
623
|
+
"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**. Use a separate key per app.\n6. Copy the key and leave it on your clipboard.\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-from-clipboard --project proj1ab2c3d4\n```\n\nUse your own app slug and project id. Add `--workspace <slug>` if needed. Keep the key out of chat.\n\nSprid reads the key from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the key is copied and it runs this for you. Typing it yourself, copy the key last: paste the command into your terminal first, then copy the key, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the key and run it again. Without clipboard access (a remote shell), save the key to a file and pass `--key <file>` instead.\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",
|
|
624
|
+
"revision": "318e11fbdfcb6dec"
|
|
610
625
|
},
|
|
611
626
|
{
|
|
612
627
|
"id": "stripe",
|
|
613
628
|
"title": "Stripe",
|
|
614
629
|
"summary": "Read revenue and subscriptions from your Stripe account.",
|
|
615
|
-
"command": "sprid connect stripe --app <slug> --key
|
|
630
|
+
"command": "sprid connect stripe --app <slug> --key-from-clipboard",
|
|
616
631
|
"url": "https://sprid.studio/docs/connect/stripe",
|
|
617
632
|
"sections": [
|
|
618
633
|
{
|
|
@@ -625,13 +640,13 @@ export const GUIDES = [
|
|
|
625
640
|
"id": "click-path-dashboardstripecom",
|
|
626
641
|
"title": "Click path (dashboard.stripe.com)",
|
|
627
642
|
"kind": "steps",
|
|
628
|
-
"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
|
|
643
|
+
"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.\n5. Copy the key and leave it on your clipboard."
|
|
629
644
|
},
|
|
630
645
|
{
|
|
631
646
|
"id": "then-run",
|
|
632
647
|
"title": "Then run",
|
|
633
648
|
"kind": "command",
|
|
634
|
-
"markdown": "```\nsprid connect stripe --app <slug> --key
|
|
649
|
+
"markdown": "```\nsprid connect stripe --app <slug> --key-from-clipboard\n```\n\nUse your Sprid app slug. Keep the key out of chat.\n\nSprid reads the key from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the key is copied and it runs this for you. Typing it yourself, copy the key last: paste the command into your terminal first, then copy the key, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the key and run it again. Without clipboard access (a remote shell), save the key to a file and pass `--key <file>` instead."
|
|
635
650
|
},
|
|
636
651
|
{
|
|
637
652
|
"id": "how-to-check-it-worked",
|
|
@@ -670,14 +685,14 @@ export const GUIDES = [
|
|
|
670
685
|
"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)"
|
|
671
686
|
}
|
|
672
687
|
],
|
|
673
|
-
"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
|
|
674
|
-
"revision": "
|
|
688
|
+
"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.\n5. Copy the key and leave it on your clipboard.\n\n## Then run\n\n```\nsprid connect stripe --app <slug> --key-from-clipboard\n```\n\nUse your Sprid app slug. Keep the key out of chat.\n\nSprid reads the key from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the key is copied and it runs this for you. Typing it yourself, copy the key last: paste the command into your terminal first, then copy the key, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the key and run it again. Without clipboard access (a remote shell), save the key to a file and pass `--key <file>` instead.\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",
|
|
689
|
+
"revision": "91b444790a7165cf"
|
|
675
690
|
},
|
|
676
691
|
{
|
|
677
692
|
"id": "polar",
|
|
678
693
|
"title": "Polar",
|
|
679
694
|
"summary": "Read revenue and subscriptions from your Polar account.",
|
|
680
|
-
"command": "sprid connect polar --app <slug> --key
|
|
695
|
+
"command": "sprid connect polar --app <slug> --key-from-clipboard",
|
|
681
696
|
"url": "https://sprid.studio/docs/connect/polar",
|
|
682
697
|
"sections": [
|
|
683
698
|
{
|
|
@@ -690,13 +705,13 @@ export const GUIDES = [
|
|
|
690
705
|
"id": "click-path-polarsh",
|
|
691
706
|
"title": "Click path (polar.sh)",
|
|
692
707
|
"kind": "steps",
|
|
693
|
-
"markdown": "1. Open your organization → **Settings → Developers → New Organization Access Token**, and name it `Sprid`.\n2. Select **metrics:read** only.\n3. Create the token
|
|
708
|
+
"markdown": "1. Open your organization → **Settings → Developers → New Organization Access Token**, and name it `Sprid`.\n2. Select **metrics:read** only.\n3. Create the token. It is shown once.\n4. Copy the token and leave it on your clipboard."
|
|
694
709
|
},
|
|
695
710
|
{
|
|
696
711
|
"id": "then-run",
|
|
697
712
|
"title": "Then run",
|
|
698
713
|
"kind": "command",
|
|
699
|
-
"markdown": "```\nsprid connect polar --app <slug> --key
|
|
714
|
+
"markdown": "```\nsprid connect polar --app <slug> --key-from-clipboard\n```\n\nUse your Sprid app slug. A personal token covering several organizations also needs `--org <organization-id>`.\n\nSprid reads the token from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the token is copied and it runs this for you. Typing it yourself, copy the token last: paste the command into your terminal first, then copy the token, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the token and run it again. Without clipboard access (a remote shell), save the token to a file and pass `--key <file>` instead."
|
|
700
715
|
},
|
|
701
716
|
{
|
|
702
717
|
"id": "how-to-check-it-worked",
|
|
@@ -729,14 +744,14 @@ export const GUIDES = [
|
|
|
729
744
|
"markdown": "- [Metrics endpoint](https://polar.sh/docs/api-reference/metrics/get)"
|
|
730
745
|
}
|
|
731
746
|
],
|
|
732
|
-
"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
|
|
733
|
-
"revision": "
|
|
747
|
+
"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. It is shown once.\n4. Copy the token and leave it on your clipboard.\n\n## Then run\n\n```\nsprid connect polar --app <slug> --key-from-clipboard\n```\n\nUse your Sprid app slug. A personal token covering several organizations also needs `--org <organization-id>`.\n\nSprid reads the token from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the token is copied and it runs this for you. Typing it yourself, copy the token last: paste the command into your terminal first, then copy the token, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the token and run it again. Without clipboard access (a remote shell), save the token to a file and pass `--key <file>` instead.\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",
|
|
748
|
+
"revision": "28cc3ae9fa0ab4d9"
|
|
734
749
|
},
|
|
735
750
|
{
|
|
736
751
|
"id": "lemonsqueezy",
|
|
737
752
|
"title": "Lemon Squeezy",
|
|
738
753
|
"summary": "Read sales and subscriptions from your Lemon Squeezy store.",
|
|
739
|
-
"command": "sprid connect lemonsqueezy --app <slug> --key
|
|
754
|
+
"command": "sprid connect lemonsqueezy --app <slug> --key-from-clipboard --store 12345",
|
|
740
755
|
"url": "https://sprid.studio/docs/connect/lemonsqueezy",
|
|
741
756
|
"sections": [
|
|
742
757
|
{
|
|
@@ -749,13 +764,13 @@ export const GUIDES = [
|
|
|
749
764
|
"id": "click-path-applemonsqueezycom",
|
|
750
765
|
"title": "Click path (app.lemonsqueezy.com)",
|
|
751
766
|
"kind": "steps",
|
|
752
|
-
"markdown": "1. **Settings → API**: click **+** and name the key `Sprid`.\n2.
|
|
767
|
+
"markdown": "1. **Settings → API**: click **+** and name the key `Sprid`.\n2. Copy the key and leave it on your clipboard. It is shown once.\n3. **Settings → Stores**: select your store and copy its numeric id from the page address."
|
|
753
768
|
},
|
|
754
769
|
{
|
|
755
770
|
"id": "then-run",
|
|
756
771
|
"title": "Then run",
|
|
757
772
|
"kind": "command",
|
|
758
|
-
"markdown": "```\nsprid connect lemonsqueezy --app <slug> --key
|
|
773
|
+
"markdown": "```\nsprid connect lemonsqueezy --app <slug> --key-from-clipboard --store 12345\n```\n\nUse your Sprid app slug and your store id. The store id is required.\n\nSprid reads the key from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the key is copied and it runs this for you. Typing it yourself, copy the key last: paste the command into your terminal first, then copy the key, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the key and run it again. Without clipboard access (a remote shell), save the key to a file and pass `--key <file>` instead."
|
|
759
774
|
},
|
|
760
775
|
{
|
|
761
776
|
"id": "how-to-check-it-worked",
|
|
@@ -788,14 +803,14 @@ export const GUIDES = [
|
|
|
788
803
|
"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)"
|
|
789
804
|
}
|
|
790
805
|
],
|
|
791
|
-
"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.
|
|
792
|
-
"revision": "
|
|
806
|
+
"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. Copy the key and leave it on your clipboard. 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-from-clipboard --store 12345\n```\n\nUse your Sprid app slug and your store id. The store id is required.\n\nSprid reads the key from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the key is copied and it runs this for you. Typing it yourself, copy the key last: paste the command into your terminal first, then copy the key, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the key and run it again. Without clipboard access (a remote shell), save the key to a file and pass `--key <file>` instead.\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",
|
|
807
|
+
"revision": "eb53db72b6df0ee0"
|
|
793
808
|
},
|
|
794
809
|
{
|
|
795
810
|
"id": "paddle",
|
|
796
811
|
"title": "Paddle",
|
|
797
812
|
"summary": "Read revenue and subscriptions from your Paddle account.",
|
|
798
|
-
"command": "sprid connect paddle --app <slug> --key
|
|
813
|
+
"command": "sprid connect paddle --app <slug> --key-from-clipboard",
|
|
799
814
|
"url": "https://sprid.studio/docs/connect/paddle",
|
|
800
815
|
"sections": [
|
|
801
816
|
{
|
|
@@ -808,13 +823,13 @@ export const GUIDES = [
|
|
|
808
823
|
"id": "click-path-vendorspaddlecom",
|
|
809
824
|
"title": "Click path (vendors.paddle.com)",
|
|
810
825
|
"kind": "steps",
|
|
811
|
-
"markdown": "1. **Developer tools → Authentication → New API key**, named `Sprid`.\n2. Select **transaction.read** and **subscription.read** only.\n3. Create the key
|
|
826
|
+
"markdown": "1. **Developer tools → Authentication → New API key**, named `Sprid`.\n2. Select **transaction.read** and **subscription.read** only.\n3. Create the key. It is shown once.\n4. Copy the key and leave it on your clipboard."
|
|
812
827
|
},
|
|
813
828
|
{
|
|
814
829
|
"id": "then-run",
|
|
815
830
|
"title": "Then run",
|
|
816
831
|
"kind": "command",
|
|
817
|
-
"markdown": "```\nsprid connect paddle --app <slug> --key
|
|
832
|
+
"markdown": "```\nsprid connect paddle --app <slug> --key-from-clipboard\n```\n\nUse your Sprid app slug. Add `--env sandbox` for a sandbox key.\n\nSprid reads the key from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the key is copied and it runs this for you. Typing it yourself, copy the key last: paste the command into your terminal first, then copy the key, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the key and run it again. Without clipboard access (a remote shell), save the key to a file and pass `--key <file>` instead."
|
|
818
833
|
},
|
|
819
834
|
{
|
|
820
835
|
"id": "how-to-check-it-worked",
|
|
@@ -847,14 +862,14 @@ export const GUIDES = [
|
|
|
847
862
|
"markdown": "- [List transactions](https://developer.paddle.com/api-reference/transactions/list-transactions)\n- [API authentication](https://developer.paddle.com/api-reference/about/authentication)"
|
|
848
863
|
}
|
|
849
864
|
],
|
|
850
|
-
"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
|
|
851
|
-
"revision": "
|
|
865
|
+
"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. It is shown once.\n4. Copy the key and leave it on your clipboard.\n\n## Then run\n\n```\nsprid connect paddle --app <slug> --key-from-clipboard\n```\n\nUse your Sprid app slug. Add `--env sandbox` for a sandbox key.\n\nSprid reads the key from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the key is copied and it runs this for you. Typing it yourself, copy the key last: paste the command into your terminal first, then copy the key, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the key and run it again. Without clipboard access (a remote shell), save the key to a file and pass `--key <file>` instead.\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",
|
|
866
|
+
"revision": "ea9a2a09dfef8f80"
|
|
852
867
|
},
|
|
853
868
|
{
|
|
854
869
|
"id": "cloudflare",
|
|
855
870
|
"title": "Cloudflare (optional)",
|
|
856
871
|
"summary": "Read traffic reports for your website.",
|
|
857
|
-
"command": "sprid connect cloudflare --
|
|
872
|
+
"command": "sprid connect cloudflare --key-from-clipboard --zone 0123456789abcdef0123456789abcdef",
|
|
858
873
|
"url": "https://sprid.studio/docs/connect/cloudflare",
|
|
859
874
|
"sections": [
|
|
860
875
|
{
|
|
@@ -867,7 +882,7 @@ export const GUIDES = [
|
|
|
867
882
|
"id": "click-path-dashcloudflarecom",
|
|
868
883
|
"title": "Click path (dash.cloudflare.com)",
|
|
869
884
|
"kind": "steps",
|
|
870
|
-
"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.
|
|
885
|
+
"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. Copy the token and leave it on your clipboard. 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."
|
|
871
886
|
},
|
|
872
887
|
{
|
|
873
888
|
"id": "zone-id",
|
|
@@ -879,7 +894,7 @@ export const GUIDES = [
|
|
|
879
894
|
"id": "then-run",
|
|
880
895
|
"title": "Then run",
|
|
881
896
|
"kind": "command",
|
|
882
|
-
"markdown": "```\nsprid connect cloudflare --
|
|
897
|
+
"markdown": "```\nsprid connect cloudflare --key-from-clipboard --zone 0123456789abcdef0123456789abcdef\n```\n\nUse your own Zone ID. Keep the token out of chat.\n\nSprid reads the token from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the token is copied and it runs this for you. Typing it yourself, copy the token last: paste the command into your terminal first, then copy the token, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the token and run it again. Without clipboard access (a remote shell), save the token to a file and pass `--token <file>` instead."
|
|
883
898
|
},
|
|
884
899
|
{
|
|
885
900
|
"id": "how-to-check-it-worked",
|
|
@@ -906,8 +921,8 @@ export const GUIDES = [
|
|
|
906
921
|
"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/)"
|
|
907
922
|
}
|
|
908
923
|
],
|
|
909
|
-
"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.
|
|
910
|
-
"revision": "
|
|
924
|
+
"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. Copy the token and leave it on your clipboard. 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 --key-from-clipboard --zone 0123456789abcdef0123456789abcdef\n```\n\nUse your own Zone ID. Keep the token out of chat.\n\nSprid reads the token from your clipboard, saves it and clears the clipboard, so it never shows on screen or lands in a file. Working with an agent? Tell it the token is copied and it runs this for you. Typing it yourself, copy the token last: paste the command into your terminal first, then copy the token, then press Enter. If the clipboard still holds the command, Sprid refuses it; copy the token and run it again. Without clipboard access (a remote shell), save the token to a file and pass `--token <file>` instead.\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",
|
|
925
|
+
"revision": "c34f4dd6420981c8"
|
|
911
926
|
},
|
|
912
927
|
{
|
|
913
928
|
"id": "instagram",
|
|
@@ -1021,6 +1036,59 @@ export const GUIDES = [
|
|
|
1021
1036
|
"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",
|
|
1022
1037
|
"revision": "710f233b9a644bb3"
|
|
1023
1038
|
},
|
|
1039
|
+
{
|
|
1040
|
+
"id": "tiktok-comments",
|
|
1041
|
+
"title": "TikTok comments",
|
|
1042
|
+
"summary": "Read the comments on your TikTok posts into the Inbox next to Instagram and YouTube, and send the replies you approve. With TikTok Ads connected too, comments on a post you promote are read from the ad account and marked as an ad; replying to them still needs this connection.",
|
|
1043
|
+
"command": "sprid connect tiktok-comments --account <slug>",
|
|
1044
|
+
"url": "https://sprid.studio/docs/connect/tiktok-comments",
|
|
1045
|
+
"sections": [
|
|
1046
|
+
{
|
|
1047
|
+
"id": "you-need",
|
|
1048
|
+
"title": "You need",
|
|
1049
|
+
"kind": "requirements",
|
|
1050
|
+
"markdown": "- The TikTok account that publishes your posts, and its login. Sign in as that account, not as a TikTok for Business or ad account user.\n- TikTok publishing already connected in Sprid, so Sprid knows which posts are yours. Sprid only reads comments on posts it published.\n\nThis is a separate connection from TikTok publishing and from TikTok Ads. The publishing connection cannot read comments. TikTok only allows comment access through its business tools."
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
"id": "then-run",
|
|
1054
|
+
"title": "Then run",
|
|
1055
|
+
"kind": "command",
|
|
1056
|
+
"markdown": "```sh\nsprid connect tiktok-comments --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug. You can also use **Settings → Accounts → [account] → TikTok comments** in Sprid."
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
"id": "click-path-the-connect",
|
|
1060
|
+
"title": "Click path (the connect)",
|
|
1061
|
+
"kind": "steps",
|
|
1062
|
+
"markdown": "1. Sign in to TikTok with the account that publishes the posts.\n2. Review the requested access (your profile and your comments) and click **Authorize**.\n3. Back in Sprid, check the connected handle.\n\nWith several brands, connect each Sprid account separately, signed in as that brand's TikTok account."
|
|
1063
|
+
},
|
|
1064
|
+
{
|
|
1065
|
+
"id": "how-to-check-it-worked",
|
|
1066
|
+
"title": "How to check it worked",
|
|
1067
|
+
"kind": "verify",
|
|
1068
|
+
"markdown": "Run `sprid status` and check that TikTok comments shows the right handle. New comments appear in the Inbox within about an hour. A comment on a promoted post is marked as an ad."
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
"id": "what-sprid-does-and-does-not-do",
|
|
1072
|
+
"title": "What Sprid does and does not do",
|
|
1073
|
+
"kind": "detail",
|
|
1074
|
+
"markdown": "- It reads comments hourly for posts from the last 3 days, and every 6 hours for posts from the last 30.\n- A reply goes out only when you send it from the Inbox. Sprid never replies on its own.\n- Sprid never hides or deletes a TikTok comment. Filing a comment as done only changes the Inbox.\n- It sees at most three replies under each comment, the limit TikTok sets."
|
|
1075
|
+
},
|
|
1076
|
+
{
|
|
1077
|
+
"id": "if-it-fails",
|
|
1078
|
+
"title": "If it fails",
|
|
1079
|
+
"kind": "troubleshooting",
|
|
1080
|
+
"markdown": "- **“TikTok comments are not switched on in Sprid yet”:** Sprid has not enabled TikTok's business tools on this server. Contact [Sprid support](mailto:hello@sprid.studio).\n- **The Inbox shows no TikTok comments:** check that the handle under TikTok comments is the account that published the posts. Posts sent to your TikTok drafts, or still in TikTok's review, have no public post yet and are skipped until they do.\n- **“Sprid answers TikTok comments through the account that posted them”:** a comment on a promoted post can only be answered with TikTok comments connected. Connect it, or reply in the TikTok app.\n- **TikTok comments shows “Reconnect”:** TikTok's access lasts about a year and then needs one new sign-in. Run the connect command again."
|
|
1081
|
+
},
|
|
1082
|
+
{
|
|
1083
|
+
"id": "sources",
|
|
1084
|
+
"title": "Sources",
|
|
1085
|
+
"kind": "sources",
|
|
1086
|
+
"markdown": "- [TikTok API for Business: TikTok account authorization](https://business-api.tiktok.com/portal/docs?id=1738083939371009)\n- [TikTok API for Business: get comments](https://business-api.tiktok.com/portal/docs?id=1760232109619202)\n- [TikTok API for Business: reply to a comment](https://business-api.tiktok.com/portal/docs?id=1762228448779266)"
|
|
1087
|
+
}
|
|
1088
|
+
],
|
|
1089
|
+
"markdown": "# TikTok comments\n\n**What Sprid does with this:** Read the comments on your TikTok posts into the Inbox next to Instagram and YouTube, and send the replies you approve. With TikTok Ads connected too, comments on a post you promote are read from the ad account and marked as an ad; replying to them still needs this connection.\n\n## You need\n\n- The TikTok account that publishes your posts, and its login. Sign in as that account, not as a TikTok for Business or ad account user.\n- TikTok publishing already connected in Sprid, so Sprid knows which posts are yours. Sprid only reads comments on posts it published.\n\nThis is a separate connection from TikTok publishing and from TikTok Ads. The publishing connection cannot read comments. TikTok only allows comment access through its business tools.\n\n## Then run\n\n```sh\nsprid connect tiktok-comments --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug. You can also use **Settings → Accounts → [account] → TikTok comments** in Sprid.\n\n## Click path (the connect)\n\n1. Sign in to TikTok with the account that publishes the posts.\n2. Review the requested access (your profile and your comments) and click **Authorize**.\n3. Back in Sprid, check the connected handle.\n\nWith several brands, connect each Sprid account separately, signed in as that brand's TikTok account.\n\n## How to check it worked\n\nRun `sprid status` and check that TikTok comments shows the right handle. New comments appear in the Inbox within about an hour. A comment on a promoted post is marked as an ad.\n\n## What Sprid does and does not do\n\n- It reads comments hourly for posts from the last 3 days, and every 6 hours for posts from the last 30.\n- A reply goes out only when you send it from the Inbox. Sprid never replies on its own.\n- Sprid never hides or deletes a TikTok comment. Filing a comment as done only changes the Inbox.\n- It sees at most three replies under each comment, the limit TikTok sets.\n\n## If it fails\n\n- **“TikTok comments are not switched on in Sprid yet”:** Sprid has not enabled TikTok's business tools on this server. Contact [Sprid support](mailto:hello@sprid.studio).\n- **The Inbox shows no TikTok comments:** check that the handle under TikTok comments is the account that published the posts. Posts sent to your TikTok drafts, or still in TikTok's review, have no public post yet and are skipped until they do.\n- **“Sprid answers TikTok comments through the account that posted them”:** a comment on a promoted post can only be answered with TikTok comments connected. Connect it, or reply in the TikTok app.\n- **TikTok comments shows “Reconnect”:** TikTok's access lasts about a year and then needs one new sign-in. Run the connect command again.\n\n## Sources\n\n- [TikTok API for Business: TikTok account authorization](https://business-api.tiktok.com/portal/docs?id=1738083939371009)\n- [TikTok API for Business: get comments](https://business-api.tiktok.com/portal/docs?id=1760232109619202)\n- [TikTok API for Business: reply to a comment](https://business-api.tiktok.com/portal/docs?id=1762228448779266)\n",
|
|
1090
|
+
"revision": "c3e5f9ad711a1fbe"
|
|
1091
|
+
},
|
|
1024
1092
|
{
|
|
1025
1093
|
"id": "youtube",
|
|
1026
1094
|
"title": "YouTube",
|
|
@@ -1274,7 +1342,7 @@ export const GUIDES = [
|
|
|
1274
1342
|
"id": "trial-and-standard-access",
|
|
1275
1343
|
"title": "Trial and Standard access",
|
|
1276
1344
|
"kind": "detail",
|
|
1277
|
-
"markdown": "
|
|
1345
|
+
"markdown": "Public Pins need **Sprid’s** Pinterest app to have Standard access. This is Sprid’s approval, not something you apply for. Until then Sprid runs Pinterest in test mode: you can connect, pick or create boards and publish image Pins, but every Pin and board it creates is visible only to you, on your own profile. Video Pins are not available in test mode. When Standard access arrives, reconnect once: a test-mode connection cannot publish public Pins."
|
|
1278
1346
|
},
|
|
1279
1347
|
{
|
|
1280
1348
|
"id": "prepare-the-account",
|
|
@@ -1286,7 +1354,7 @@ export const GUIDES = [
|
|
|
1286
1354
|
"id": "if-it-fails",
|
|
1287
1355
|
"title": "If it fails",
|
|
1288
1356
|
"kind": "troubleshooting",
|
|
1289
|
-
"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-
|
|
1357
|
+
"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- **“Reconnect Pinterest to create test Pins” or “…to publish public Pins”:** the connection was made under the other access tier, and its token only works there. Reconnect once. See Trial and Standard access above.\n- **Boards missing in test mode:** test mode sees only boards created in test mode. Create one from the board picker.\n- **Analytics unavailable:** use a business account and check the Pin is public. Unavailable data is unavailable, not zero."
|
|
1290
1358
|
},
|
|
1291
1359
|
{
|
|
1292
1360
|
"id": "sources",
|
|
@@ -1295,8 +1363,179 @@ export const GUIDES = [
|
|
|
1295
1363
|
"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)"
|
|
1296
1364
|
}
|
|
1297
1365
|
],
|
|
1298
|
-
"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\
|
|
1299
|
-
"revision": "
|
|
1366
|
+
"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\nPublic Pins need **Sprid’s** Pinterest app to have Standard access. This is Sprid’s approval, not something you apply for. Until then Sprid runs Pinterest in test mode: you can connect, pick or create boards and publish image Pins, but every Pin and board it creates is visible only to you, on your own profile. Video Pins are not available in test mode. When Standard access arrives, reconnect once: a test-mode connection cannot publish public Pins.\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- **“Reconnect Pinterest to create test Pins” or “…to publish public Pins”:** the connection was made under the other access tier, and its token only works there. Reconnect once. See Trial and Standard access above.\n- **Boards missing in test mode:** test mode sees only boards created in test mode. Create one from the board picker.\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",
|
|
1367
|
+
"revision": "8bf9325a0d261010"
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
"id": "meta-ads",
|
|
1371
|
+
"title": "Meta Ads",
|
|
1372
|
+
"summary": "Prepare campaigns in your own Meta ad account, start them only when you confirm, and report what they spent and returned.",
|
|
1373
|
+
"command": "sprid connect meta-ads --account <slug>",
|
|
1374
|
+
"url": "https://sprid.studio/docs/connect/meta-ads",
|
|
1375
|
+
"sections": [
|
|
1376
|
+
{
|
|
1377
|
+
"id": "you-need",
|
|
1378
|
+
"title": "You need",
|
|
1379
|
+
"kind": "requirements",
|
|
1380
|
+
"markdown": "- A Facebook profile with two-factor authentication turned on. Meta will not let you add ad assets without it.\n- A **business portfolio** at [business.facebook.com](https://business.facebook.com). One per company is enough, even with several apps or brands. See **One portfolio or several** below.\n- A **Facebook Page** for the brand. Ads always run from a Page, including the ones shown on Instagram. To create one, see [Create your social accounts](https://sprid.studio/docs/connect/social-accounts).\n- An **ad account** with a payment method, one per brand.\n- Optional: a **dataset** (formerly Meta Pixel) if you want campaigns that optimise for signups or purchases.\n\nYou do **not** create a Meta developer app or copy a token. You only sign in with Facebook and pick the Page and ad account."
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
"id": "set-up-meta-once-before-you-connect",
|
|
1384
|
+
"title": "Set up Meta (once, before you connect)",
|
|
1385
|
+
"kind": "steps",
|
|
1386
|
+
"markdown": "Skip any step you have already done.\n\n1. **Business portfolio.** Go to [business.facebook.com](https://business.facebook.com) and create a business portfolio under your company’s legal name.\n2. **Page.** In the portfolio, open **Settings → Accounts → Pages** and add the brand’s Page. Give yourself access that includes **Ads**.\n3. **Ad account.** Open **Settings → Accounts → Ad accounts → Add → Create a new ad account**. Pick the **currency** and **time zone** carefully: once the account has spent anything, neither can be changed. Choose the currency of the market you advertise in. Give yourself **Manage campaigns** or full control.\n4. **Payment method.** Open **Billing & payments** for that ad account and add a card. Sprid can connect an account without one, but no campaign can start until it has one.\n5. **Dataset (optional).** Open [Events Manager](https://business.facebook.com/events_manager2) → **Connect data sources → Web**, name it after your domain and copy its **dataset id**. Sprid only needs the id. The events themselves reach it from your site or app, through the Meta Pixel or the Conversions API, and without events a conversion campaign has nothing to optimise for."
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
"id": "then-run",
|
|
1390
|
+
"title": "Then run",
|
|
1391
|
+
"kind": "command",
|
|
1392
|
+
"markdown": "```sh\nsprid connect meta-ads --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug. Add `--dataset <id>` if you created one in step 5. You can also use **Settings → Accounts → [account] → Meta Ads** in Sprid."
|
|
1393
|
+
},
|
|
1394
|
+
{
|
|
1395
|
+
"id": "click-path-the-connect",
|
|
1396
|
+
"title": "Click path (the connect)",
|
|
1397
|
+
"kind": "steps",
|
|
1398
|
+
"markdown": "1. Sign in with the Facebook profile that manages the Page and the ad account.\n2. Leave every requested permission switched on and click **Continue**.\n3. Back in Sprid, pick the **Page** your ads should appear from, then the **ad account**. Each list only appears when there is more than one to choose from.\n4. From the CLI, a profile with several Pages or ad accounts gets a message listing their ids. Run the command again with them:\n\n```sh\nsprid connect meta-ads --account <slug> --page <page id> --ad-account <ad account id> --dataset <dataset id>\n```\n\nWith several brands, connect each Sprid account separately and pick that brand’s own Page and ad account every time."
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
"id": "check-the-connection",
|
|
1402
|
+
"title": "Check the connection",
|
|
1403
|
+
"kind": "verify",
|
|
1404
|
+
"markdown": "Run `sprid status` and check that Meta Ads shows the intended Page name, followed by “(Ads)”.\n\nA campaign Sprid prepares starts **paused**. Nothing is spent until you confirm the start."
|
|
1405
|
+
},
|
|
1406
|
+
{
|
|
1407
|
+
"id": "one-portfolio-or-several",
|
|
1408
|
+
"title": "One portfolio or several",
|
|
1409
|
+
"kind": "detail",
|
|
1410
|
+
"markdown": "Keep one business portfolio for your company and put one Page, one ad account and one dataset per brand inside it.\n\n- **A restriction on an ad account stays on that account.** The other brands keep running.\n- **Separate portfolios isolate less than they appear to.** Meta links portfolios run by the same people and paid with the same card, so a problem on one is often read as a problem on all of them. You also pay for the split: each portfolio has to be verified and set up separately, and a personal profile can only create a couple of them.\n- **What one portfolio risks:** if the portfolio itself is restricted, every brand in it stops at once. If one brand operates in a regulated category (housing, credit, employment, politics) and draws an enforcement action, move that brand to its own portfolio then.\n\nGive each ad account its own payment method where you can, and add a second admin to the portfolio so you are not locked out if your own profile has a problem."
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
"id": "if-it-fails",
|
|
1414
|
+
"title": "If it fails",
|
|
1415
|
+
"kind": "troubleshooting",
|
|
1416
|
+
"markdown": "- **“No Facebook Pages found”:** the profile you signed in with has no Page access. Check the Page in the portfolio, give yourself access and reconnect.\n- **No ad account to choose, or the wrong ones:** only active ad accounts are listed. Check that the account is not disabled or closed in Ads Manager and that your profile is assigned to it.\n- **“Re-run the connect with --page / --ad-account”:** the profile manages several. Copy the right ids from the message and run the command again with them.\n- **“Already connected to another account”:** that ad account is connected to a different Sprid account. Check you picked the right one; move it only if you mean to.\n- **Campaign refuses to start with a conversion goal:** the connection has no dataset. Reconnect with `--dataset <id>`.\n- **Campaign refuses to start at all:** add a payment method to the ad account (step 4).\n- **“Invalid Scopes”, app unavailable or “URL blocked”:** contact [Sprid support](mailto:hello@sprid.studio). Only Sprid can fix these.\n- **Expiring connection in `sprid status`:** Meta’s sign-in lasts about 60 days. Run the connect again."
|
|
1417
|
+
},
|
|
1418
|
+
{
|
|
1419
|
+
"id": "sources",
|
|
1420
|
+
"title": "Sources",
|
|
1421
|
+
"kind": "sources",
|
|
1422
|
+
"markdown": "- [Create a business portfolio](https://www.facebook.com/business/help/1710077379203657)\n- [Add an ad account to your business portfolio](https://www.facebook.com/business/help/915885887059947)\n- [Ad account limits](https://www.facebook.com/business/help/1026272311098874)\n- [Add a payment method to an ad account](https://www.facebook.com/business/help/132073386867900)\n- [Assign business assets to people](https://www.facebook.com/business/help/325571851329683)\n- [Create a dataset in Events Manager](https://www.facebook.com/business/help/5818684664831465)\n- [About the Conversions API](https://www.facebook.com/business/help/AboutConversionsAPI)"
|
|
1423
|
+
}
|
|
1424
|
+
],
|
|
1425
|
+
"markdown": "# Meta Ads\n\n**What Sprid does with this:** Prepare campaigns in your own Meta ad account, start them only when you confirm, and report what they spent and returned.\n\n## You need\n\n- A Facebook profile with two-factor authentication turned on. Meta will not let you add ad assets without it.\n- A **business portfolio** at [business.facebook.com](https://business.facebook.com). One per company is enough, even with several apps or brands. See **One portfolio or several** below.\n- A **Facebook Page** for the brand. Ads always run from a Page, including the ones shown on Instagram. To create one, see [Create your social accounts](https://sprid.studio/docs/connect/social-accounts).\n- An **ad account** with a payment method, one per brand.\n- Optional: a **dataset** (formerly Meta Pixel) if you want campaigns that optimise for signups or purchases.\n\nYou do **not** create a Meta developer app or copy a token. You only sign in with Facebook and pick the Page and ad account.\n\n## Set up Meta (once, before you connect)\n\nSkip any step you have already done.\n\n1. **Business portfolio.** Go to [business.facebook.com](https://business.facebook.com) and create a business portfolio under your company’s legal name.\n2. **Page.** In the portfolio, open **Settings → Accounts → Pages** and add the brand’s Page. Give yourself access that includes **Ads**.\n3. **Ad account.** Open **Settings → Accounts → Ad accounts → Add → Create a new ad account**. Pick the **currency** and **time zone** carefully: once the account has spent anything, neither can be changed. Choose the currency of the market you advertise in. Give yourself **Manage campaigns** or full control.\n4. **Payment method.** Open **Billing & payments** for that ad account and add a card. Sprid can connect an account without one, but no campaign can start until it has one.\n5. **Dataset (optional).** Open [Events Manager](https://business.facebook.com/events_manager2) → **Connect data sources → Web**, name it after your domain and copy its **dataset id**. Sprid only needs the id. The events themselves reach it from your site or app, through the Meta Pixel or the Conversions API, and without events a conversion campaign has nothing to optimise for.\n\n## Then run\n\n```sh\nsprid connect meta-ads --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug. Add `--dataset <id>` if you created one in step 5. You can also use **Settings → Accounts → [account] → Meta Ads** in Sprid.\n\n## Click path (the connect)\n\n1. Sign in with the Facebook profile that manages the Page and the ad account.\n2. Leave every requested permission switched on and click **Continue**.\n3. Back in Sprid, pick the **Page** your ads should appear from, then the **ad account**. Each list only appears when there is more than one to choose from.\n4. From the CLI, a profile with several Pages or ad accounts gets a message listing their ids. Run the command again with them:\n\n```sh\nsprid connect meta-ads --account <slug> --page <page id> --ad-account <ad account id> --dataset <dataset id>\n```\n\nWith several brands, connect each Sprid account separately and pick that brand’s own Page and ad account every time.\n\n## Check the connection\n\nRun `sprid status` and check that Meta Ads shows the intended Page name, followed by “(Ads)”.\n\nA campaign Sprid prepares starts **paused**. Nothing is spent until you confirm the start.\n\n## One portfolio or several\n\nKeep one business portfolio for your company and put one Page, one ad account and one dataset per brand inside it.\n\n- **A restriction on an ad account stays on that account.** The other brands keep running.\n- **Separate portfolios isolate less than they appear to.** Meta links portfolios run by the same people and paid with the same card, so a problem on one is often read as a problem on all of them. You also pay for the split: each portfolio has to be verified and set up separately, and a personal profile can only create a couple of them.\n- **What one portfolio risks:** if the portfolio itself is restricted, every brand in it stops at once. If one brand operates in a regulated category (housing, credit, employment, politics) and draws an enforcement action, move that brand to its own portfolio then.\n\nGive each ad account its own payment method where you can, and add a second admin to the portfolio so you are not locked out if your own profile has a problem.\n\n## If it fails\n\n- **“No Facebook Pages found”:** the profile you signed in with has no Page access. Check the Page in the portfolio, give yourself access and reconnect.\n- **No ad account to choose, or the wrong ones:** only active ad accounts are listed. Check that the account is not disabled or closed in Ads Manager and that your profile is assigned to it.\n- **“Re-run the connect with --page / --ad-account”:** the profile manages several. Copy the right ids from the message and run the command again with them.\n- **“Already connected to another account”:** that ad account is connected to a different Sprid account. Check you picked the right one; move it only if you mean to.\n- **Campaign refuses to start with a conversion goal:** the connection has no dataset. Reconnect with `--dataset <id>`.\n- **Campaign refuses to start at all:** add a payment method to the ad account (step 4).\n- **“Invalid Scopes”, app unavailable or “URL blocked”:** contact [Sprid support](mailto:hello@sprid.studio). Only Sprid can fix these.\n- **Expiring connection in `sprid status`:** Meta’s sign-in lasts about 60 days. Run the connect again.\n\n## Sources\n\n- [Create a business portfolio](https://www.facebook.com/business/help/1710077379203657)\n- [Add an ad account to your business portfolio](https://www.facebook.com/business/help/915885887059947)\n- [Ad account limits](https://www.facebook.com/business/help/1026272311098874)\n- [Add a payment method to an ad account](https://www.facebook.com/business/help/132073386867900)\n- [Assign business assets to people](https://www.facebook.com/business/help/325571851329683)\n- [Create a dataset in Events Manager](https://www.facebook.com/business/help/5818684664831465)\n- [About the Conversions API](https://www.facebook.com/business/help/AboutConversionsAPI)\n",
|
|
1426
|
+
"revision": "ee377cb02a88bda4"
|
|
1427
|
+
},
|
|
1428
|
+
{
|
|
1429
|
+
"id": "google-ads",
|
|
1430
|
+
"title": "Google Ads",
|
|
1431
|
+
"summary": "Promote your published YouTube videos from your own Google Ads account, start each campaign only when you confirm, and report what it spent and how many people watched.",
|
|
1432
|
+
"command": "sprid connect google-ads --account <slug>",
|
|
1433
|
+
"url": "https://sprid.studio/docs/connect/google-ads",
|
|
1434
|
+
"sections": [
|
|
1435
|
+
{
|
|
1436
|
+
"id": "you-need",
|
|
1437
|
+
"title": "You need",
|
|
1438
|
+
"kind": "requirements",
|
|
1439
|
+
"markdown": "- A **Google Ads account** with a payment method. It can be the same Google login as your YouTube channel or a different one.\n- Access to that ad account as **Standard** or **Admin**. Read-only and Billing access cannot create campaigns.\n- A **YouTube video published through Sprid** that is **public** or **unlisted**. Google does not run private videos as ads.\n- A **channel picture** on that YouTube channel. Google shows it as the ad’s logo and refuses an ad without one.\n\nYou do **not** create a Google Cloud project, a developer token or an API key. You only sign in with Google and pick the ad account."
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
"id": "set-up-google-ads-once-before-you-connect",
|
|
1443
|
+
"title": "Set up Google Ads (once, before you connect)",
|
|
1444
|
+
"kind": "steps",
|
|
1445
|
+
"markdown": "Skip any step you have already done.\n\n1. **Ad account.** Go to [ads.google.com](https://ads.google.com) and create an account. Pick the **currency** and **time zone** carefully: neither can be changed later.\n2. **Payment method.** Open **Billing → Settings** in that account and add a payment method. Sprid can connect an account without one, but no campaign can start until it has one.\n3. **Access.** If someone else owns the ad account, ask them to add your Google login under **Admin → Access and security** with **Standard** or **Admin** access."
|
|
1446
|
+
},
|
|
1447
|
+
{
|
|
1448
|
+
"id": "then-run",
|
|
1449
|
+
"title": "Then run",
|
|
1450
|
+
"kind": "command",
|
|
1451
|
+
"markdown": "```sh\nsprid connect google-ads --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug."
|
|
1452
|
+
},
|
|
1453
|
+
{
|
|
1454
|
+
"id": "click-path-the-connect",
|
|
1455
|
+
"title": "Click path (the connect)",
|
|
1456
|
+
"kind": "steps",
|
|
1457
|
+
"markdown": "1. Sign in with the Google login that has access to the ad account.\n2. Google asks to let Sprid **see, edit, create and delete your Google Ads accounts and data**. Click **Continue**. Sprid only creates the campaigns you review, and creates them paused.\n3. If the login can manage more than one ad account, Sprid lists their ids. Connect again with the one this brand should use:\n\n```sh\nsprid connect google-ads --account <slug> --ad-account <customer id>\n```\n\nThe customer id is the ten-digit number at the top right of Google Ads, for example `123-456-7890`."
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
"id": "check-the-connection",
|
|
1461
|
+
"title": "Check the connection",
|
|
1462
|
+
"kind": "verify",
|
|
1463
|
+
"markdown": "Run `sprid status` and check that the ad account’s name shows, followed by “(Google Ads)”.\n\nWhen you promote a YouTube video, Sprid prepares a **Demand Gen** campaign that shows the video on YouTube (in-stream, in-feed and Shorts) in the countries you pick, at the daily budget you review. It starts **paused**. Nothing is spent until you confirm the start. Google reviews the ad before it runs, usually within a day.\n\nWhat Sprid sends Google for the ad: the video, its title as the headline, the first line of its caption as the description, the channel name as the business name and the channel picture as the logo. Hashtags are left out."
|
|
1464
|
+
},
|
|
1465
|
+
{
|
|
1466
|
+
"id": "if-it-fails",
|
|
1467
|
+
"title": "If it fails",
|
|
1468
|
+
"kind": "troubleshooting",
|
|
1469
|
+
"markdown": "- **“Google Ads promotion is not switched on in Sprid yet”:** Sprid is waiting on Google’s approval of its own access. Nothing on your side needs to change.\n- **“This Google login has no active Google Ads account”:** the login you signed in with has no ad account, or only cancelled ones. Create one (step 1) or sign in with the login that has access.\n- **Your ad account is missing from the list:** only active accounts you can open directly, or directly under a manager account you can open, are listed. Ask the owner to add your login to the ad account itself.\n- **“Google returned no refresh token”:** remove Sprid at [myaccount.google.com/permissions](https://myaccount.google.com/permissions), then connect again.\n- **“This Google login cannot manage the connected Google Ads account”:** your access is read-only or billing-only. Ask for Standard access and reconnect.\n- **A campaign refuses to start:** add a payment method (step 2). Sprid shows this before you pick a budget when Google reports no approved billing.\n- **“Google Ads needs the YouTube channel’s picture as the ad’s logo”:** add a channel picture in **YouTube Studio → Customization → Branding**, reconnect YouTube, then promote again.\n- **“Google Ads cannot target …”:** Google does not advertise in that country. Remove it and try again.\n- **Political or gambling content:** Google requires advertiser verification or a certification Sprid cannot hold for you. Promote those from Google Ads directly.\n- **The ad is disapproved:** Sprid shows Google’s policy reason on the promotion. Open the campaign in Google Ads to appeal or edit.\n- **“Invalid scope”, app unavailable or “redirect_uri_mismatch”:** contact [Sprid support](mailto:hello@sprid.studio). Only Sprid can fix these."
|
|
1470
|
+
},
|
|
1471
|
+
{
|
|
1472
|
+
"id": "sources",
|
|
1473
|
+
"title": "Sources",
|
|
1474
|
+
"kind": "sources",
|
|
1475
|
+
"markdown": "- [Create a Google Ads account](https://support.google.com/google-ads/answer/6366720)\n- [Add a payment method](https://support.google.com/google-ads/answer/2375375)\n- [Access levels in your Google Ads account](https://support.google.com/google-ads/answer/9978556)\n- [About Demand Gen campaigns](https://support.google.com/google-ads/answer/13695777)\n- [Create a Demand Gen campaign (API)](https://developers.google.com/google-ads/api/docs/demand-gen/create-campaign)\n- [Google Ads API OAuth scope](https://developers.google.com/google-ads/api/docs/oauth/overview)"
|
|
1476
|
+
}
|
|
1477
|
+
],
|
|
1478
|
+
"markdown": "# Google Ads\n\n**What Sprid does with this:** Promote your published YouTube videos from your own Google Ads account, start each campaign only when you confirm, and report what it spent and how many people watched.\n\n## You need\n\n- A **Google Ads account** with a payment method. It can be the same Google login as your YouTube channel or a different one.\n- Access to that ad account as **Standard** or **Admin**. Read-only and Billing access cannot create campaigns.\n- A **YouTube video published through Sprid** that is **public** or **unlisted**. Google does not run private videos as ads.\n- A **channel picture** on that YouTube channel. Google shows it as the ad’s logo and refuses an ad without one.\n\nYou do **not** create a Google Cloud project, a developer token or an API key. You only sign in with Google and pick the ad account.\n\n## Set up Google Ads (once, before you connect)\n\nSkip any step you have already done.\n\n1. **Ad account.** Go to [ads.google.com](https://ads.google.com) and create an account. Pick the **currency** and **time zone** carefully: neither can be changed later.\n2. **Payment method.** Open **Billing → Settings** in that account and add a payment method. Sprid can connect an account without one, but no campaign can start until it has one.\n3. **Access.** If someone else owns the ad account, ask them to add your Google login under **Admin → Access and security** with **Standard** or **Admin** access.\n\n## Then run\n\n```sh\nsprid connect google-ads --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug.\n\n## Click path (the connect)\n\n1. Sign in with the Google login that has access to the ad account.\n2. Google asks to let Sprid **see, edit, create and delete your Google Ads accounts and data**. Click **Continue**. Sprid only creates the campaigns you review, and creates them paused.\n3. If the login can manage more than one ad account, Sprid lists their ids. Connect again with the one this brand should use:\n\n```sh\nsprid connect google-ads --account <slug> --ad-account <customer id>\n```\n\nThe customer id is the ten-digit number at the top right of Google Ads, for example `123-456-7890`.\n\n## Check the connection\n\nRun `sprid status` and check that the ad account’s name shows, followed by “(Google Ads)”.\n\nWhen you promote a YouTube video, Sprid prepares a **Demand Gen** campaign that shows the video on YouTube (in-stream, in-feed and Shorts) in the countries you pick, at the daily budget you review. It starts **paused**. Nothing is spent until you confirm the start. Google reviews the ad before it runs, usually within a day.\n\nWhat Sprid sends Google for the ad: the video, its title as the headline, the first line of its caption as the description, the channel name as the business name and the channel picture as the logo. Hashtags are left out.\n\n## If it fails\n\n- **“Google Ads promotion is not switched on in Sprid yet”:** Sprid is waiting on Google’s approval of its own access. Nothing on your side needs to change.\n- **“This Google login has no active Google Ads account”:** the login you signed in with has no ad account, or only cancelled ones. Create one (step 1) or sign in with the login that has access.\n- **Your ad account is missing from the list:** only active accounts you can open directly, or directly under a manager account you can open, are listed. Ask the owner to add your login to the ad account itself.\n- **“Google returned no refresh token”:** remove Sprid at [myaccount.google.com/permissions](https://myaccount.google.com/permissions), then connect again.\n- **“This Google login cannot manage the connected Google Ads account”:** your access is read-only or billing-only. Ask for Standard access and reconnect.\n- **A campaign refuses to start:** add a payment method (step 2). Sprid shows this before you pick a budget when Google reports no approved billing.\n- **“Google Ads needs the YouTube channel’s picture as the ad’s logo”:** add a channel picture in **YouTube Studio → Customization → Branding**, reconnect YouTube, then promote again.\n- **“Google Ads cannot target …”:** Google does not advertise in that country. Remove it and try again.\n- **Political or gambling content:** Google requires advertiser verification or a certification Sprid cannot hold for you. Promote those from Google Ads directly.\n- **The ad is disapproved:** Sprid shows Google’s policy reason on the promotion. Open the campaign in Google Ads to appeal or edit.\n- **“Invalid scope”, app unavailable or “redirect_uri_mismatch”:** contact [Sprid support](mailto:hello@sprid.studio). Only Sprid can fix these.\n\n## Sources\n\n- [Create a Google Ads account](https://support.google.com/google-ads/answer/6366720)\n- [Add a payment method](https://support.google.com/google-ads/answer/2375375)\n- [Access levels in your Google Ads account](https://support.google.com/google-ads/answer/9978556)\n- [About Demand Gen campaigns](https://support.google.com/google-ads/answer/13695777)\n- [Create a Demand Gen campaign (API)](https://developers.google.com/google-ads/api/docs/demand-gen/create-campaign)\n- [Google Ads API OAuth scope](https://developers.google.com/google-ads/api/docs/oauth/overview)\n",
|
|
1479
|
+
"revision": "169d6e428b3a258c"
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
"id": "tiktok-ads",
|
|
1483
|
+
"title": "TikTok Ads",
|
|
1484
|
+
"summary": "Promote a post that is already live on TikTok as a Spark Ad from your own TikTok ad account, start it only when you confirm, and report what it spent and reached.",
|
|
1485
|
+
"command": "sprid connect tiktok-ads --account <slug>",
|
|
1486
|
+
"url": "https://sprid.studio/docs/connect/tiktok-ads",
|
|
1487
|
+
"sections": [
|
|
1488
|
+
{
|
|
1489
|
+
"id": "you-need",
|
|
1490
|
+
"title": "You need",
|
|
1491
|
+
"kind": "requirements",
|
|
1492
|
+
"markdown": "- A **TikTok for Business** login at [ads.tiktok.com](https://ads.tiktok.com), with an **ad account** you manage. This is a different login from your TikTok app account, and a different connection from TikTok publishing in Sprid.\n- A **payment method** or a prepaid balance on that ad account.\n- For each post you promote, its **ad authorization code**, generated in the TikTok app by whoever owns the post. See **Get a post's ad authorization code** below. Once a code is applied, Sprid remembers the post until the code expires.\n\nYou do **not** create a TikTok developer app or copy a token. You only sign in to TikTok for Business and pick the ad account."
|
|
1493
|
+
},
|
|
1494
|
+
{
|
|
1495
|
+
"id": "set-up-tiktok-for-business-once-before-you-connect",
|
|
1496
|
+
"title": "Set up TikTok for Business (once, before you connect)",
|
|
1497
|
+
"kind": "steps",
|
|
1498
|
+
"markdown": "Skip any step you have already done.\n\n1. **Ad account.** Sign in at [ads.tiktok.com](https://ads.tiktok.com) and create an ad account. Pick the **currency** and **time zone** carefully: neither can be changed later. Sprid’s budget controls support currencies with two decimals (USD, EUR, SEK and most others).\n2. **Payment.** In TikTok Ads Manager open **Account → Payment** and add a card or funds. Sprid can connect an account without one, but nothing can deliver until it can pay.\n3. **Account review.** A new ad account is reviewed by TikTok before it can run anything. Sprid tells you when the account is still under review."
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
"id": "then-run",
|
|
1502
|
+
"title": "Then run",
|
|
1503
|
+
"kind": "command",
|
|
1504
|
+
"markdown": "```sh\nsprid connect tiktok-ads --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug. You can also use **Settings → Accounts → [account] → TikTok Ads** in Sprid."
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
"id": "click-path-the-connect",
|
|
1508
|
+
"title": "Click path (the connect)",
|
|
1509
|
+
"kind": "steps",
|
|
1510
|
+
"markdown": "1. Sign in with the TikTok for Business login that manages the ad account.\n2. Choose the ad account, leave every requested permission switched on and click **Confirm**.\n3. Back in Sprid, pick the **ad account** if the login manages more than one. The list only appears when there is a choice.\n\nWith several brands, connect each Sprid account separately and pick that brand’s own ad account every time."
|
|
1511
|
+
},
|
|
1512
|
+
{
|
|
1513
|
+
"id": "get-a-posts-ad-authorization-code",
|
|
1514
|
+
"title": "Get a post's ad authorization code",
|
|
1515
|
+
"kind": "detail",
|
|
1516
|
+
"markdown": "A Spark Ad runs the post as itself, under the creator’s account, so the creator has to allow it. In the TikTok app, signed in as the account that published the post:\n\n1. Open the post, tap **…** (or the share arrow), then **Ad settings**.\n2. Turn on **Ad authorization**. The first time, TikTok may ask you to turn it on for the account in **Settings and privacy → Creator tools → Ad settings**.\n3. Tap **Generate code**, pick how long the authorization should last and copy the code.\n4. In Sprid, open **Promote** on the post and paste the code when it asks for one.\n\nPick a duration longer than the promotion. Sprid refuses a code that ends before the promotion would, because the ad would stop when it expires. TikTok offers 7, 30, 60, 180 or 365 days *(from TikTok’s help pages; the exact labels in the app may differ)*."
|
|
1517
|
+
},
|
|
1518
|
+
{
|
|
1519
|
+
"id": "check-the-connection",
|
|
1520
|
+
"title": "Check the connection",
|
|
1521
|
+
"kind": "verify",
|
|
1522
|
+
"markdown": "Run `sprid status` and check that TikTok Ads shows the intended ad account name, followed by “(TikTok Ads)”.\n\nA promotion Sprid prepares starts **paused**. Nothing is spent until you confirm the start. It runs on TikTok only, to adults (18+), in the countries you choose."
|
|
1523
|
+
},
|
|
1524
|
+
{
|
|
1525
|
+
"id": "if-it-fails",
|
|
1526
|
+
"title": "If it fails",
|
|
1527
|
+
"kind": "troubleshooting",
|
|
1528
|
+
"markdown": "- **“TikTok promotion is not switched on in Sprid yet”:** Sprid has not enabled TikTok ads on this server. Contact [Sprid support](mailto:hello@sprid.studio).\n- **“This post needs its ad authorization code”:** generate one in the TikTok app (see above) and paste it in the Promote sheet.\n- **“That authorization code belongs to a different TikTok post”:** the code was generated on another post. Generate it on the post you are promoting.\n- **“The post’s ad authorization ends … before the promotion would”:** generate a new code with a longer duration, or shorten the promotion.\n- **“This TikTok ad account is not active” or still under review:** check the account’s status in TikTok Ads Manager.\n- **“TikTok cannot show ads in …”:** that country is not available to this ad account. Remove it from the promotion.\n- **The currency is not supported:** Sprid’s budgets need a two-decimal currency. Create an ad account in a supported currency.\n- **Political content:** TikTok does not allow political ads. Sprid refuses them.\n- **Housing, credit or employment:** declare the category in the Promote sheet. TikTok then limits targeting. Outside the US and Canada TikTok may refuse the category for your account; the message says so.\n- **The ad stopped with “creator authorization revoked”:** the creator turned ad authorization off or the code expired. Generate a new code and promote the post again."
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
"id": "sources",
|
|
1532
|
+
"title": "Sources",
|
|
1533
|
+
"kind": "sources",
|
|
1534
|
+
"markdown": "- [TikTok API for Business: authorization](https://business-api.tiktok.com/portal/docs?id=1738373164380162)\n- [About Spark Ads](https://ads.tiktok.com/help/article/spark-ads)\n- [How to create Spark Ads in TikTok Ads Manager](https://ads.tiktok.com/help/article/spark-ads-creation-guide)\n- [Special ad categories on TikTok](https://ads.tiktok.com/help/article/special-ad-categories)"
|
|
1535
|
+
}
|
|
1536
|
+
],
|
|
1537
|
+
"markdown": "# TikTok Ads\n\n**What Sprid does with this:** Promote a post that is already live on TikTok as a Spark Ad from your own TikTok ad account, start it only when you confirm, and report what it spent and reached.\n\n## You need\n\n- A **TikTok for Business** login at [ads.tiktok.com](https://ads.tiktok.com), with an **ad account** you manage. This is a different login from your TikTok app account, and a different connection from TikTok publishing in Sprid.\n- A **payment method** or a prepaid balance on that ad account.\n- For each post you promote, its **ad authorization code**, generated in the TikTok app by whoever owns the post. See **Get a post's ad authorization code** below. Once a code is applied, Sprid remembers the post until the code expires.\n\nYou do **not** create a TikTok developer app or copy a token. You only sign in to TikTok for Business and pick the ad account.\n\n## Set up TikTok for Business (once, before you connect)\n\nSkip any step you have already done.\n\n1. **Ad account.** Sign in at [ads.tiktok.com](https://ads.tiktok.com) and create an ad account. Pick the **currency** and **time zone** carefully: neither can be changed later. Sprid’s budget controls support currencies with two decimals (USD, EUR, SEK and most others).\n2. **Payment.** In TikTok Ads Manager open **Account → Payment** and add a card or funds. Sprid can connect an account without one, but nothing can deliver until it can pay.\n3. **Account review.** A new ad account is reviewed by TikTok before it can run anything. Sprid tells you when the account is still under review.\n\n## Then run\n\n```sh\nsprid connect tiktok-ads --account <slug>\n```\n\nReplace `<slug>` with your Sprid account slug. You can also use **Settings → Accounts → [account] → TikTok Ads** in Sprid.\n\n## Click path (the connect)\n\n1. Sign in with the TikTok for Business login that manages the ad account.\n2. Choose the ad account, leave every requested permission switched on and click **Confirm**.\n3. Back in Sprid, pick the **ad account** if the login manages more than one. The list only appears when there is a choice.\n\nWith several brands, connect each Sprid account separately and pick that brand’s own ad account every time.\n\n## Get a post's ad authorization code\n\nA Spark Ad runs the post as itself, under the creator’s account, so the creator has to allow it. In the TikTok app, signed in as the account that published the post:\n\n1. Open the post, tap **…** (or the share arrow), then **Ad settings**.\n2. Turn on **Ad authorization**. The first time, TikTok may ask you to turn it on for the account in **Settings and privacy → Creator tools → Ad settings**.\n3. Tap **Generate code**, pick how long the authorization should last and copy the code.\n4. In Sprid, open **Promote** on the post and paste the code when it asks for one.\n\nPick a duration longer than the promotion. Sprid refuses a code that ends before the promotion would, because the ad would stop when it expires. TikTok offers 7, 30, 60, 180 or 365 days *(from TikTok’s help pages; the exact labels in the app may differ)*.\n\n## Check the connection\n\nRun `sprid status` and check that TikTok Ads shows the intended ad account name, followed by “(TikTok Ads)”.\n\nA promotion Sprid prepares starts **paused**. Nothing is spent until you confirm the start. It runs on TikTok only, to adults (18+), in the countries you choose.\n\n## If it fails\n\n- **“TikTok promotion is not switched on in Sprid yet”:** Sprid has not enabled TikTok ads on this server. Contact [Sprid support](mailto:hello@sprid.studio).\n- **“This post needs its ad authorization code”:** generate one in the TikTok app (see above) and paste it in the Promote sheet.\n- **“That authorization code belongs to a different TikTok post”:** the code was generated on another post. Generate it on the post you are promoting.\n- **“The post’s ad authorization ends … before the promotion would”:** generate a new code with a longer duration, or shorten the promotion.\n- **“This TikTok ad account is not active” or still under review:** check the account’s status in TikTok Ads Manager.\n- **“TikTok cannot show ads in …”:** that country is not available to this ad account. Remove it from the promotion.\n- **The currency is not supported:** Sprid’s budgets need a two-decimal currency. Create an ad account in a supported currency.\n- **Political content:** TikTok does not allow political ads. Sprid refuses them.\n- **Housing, credit or employment:** declare the category in the Promote sheet. TikTok then limits targeting. Outside the US and Canada TikTok may refuse the category for your account; the message says so.\n- **The ad stopped with “creator authorization revoked”:** the creator turned ad authorization off or the code expired. Generate a new code and promote the post again.\n\n## Sources\n\n- [TikTok API for Business: authorization](https://business-api.tiktok.com/portal/docs?id=1738373164380162)\n- [About Spark Ads](https://ads.tiktok.com/help/article/spark-ads)\n- [How to create Spark Ads in TikTok Ads Manager](https://ads.tiktok.com/help/article/spark-ads-creation-guide)\n- [Special ad categories on TikTok](https://ads.tiktok.com/help/article/special-ad-categories)\n",
|
|
1538
|
+
"revision": "41a9c611fbe927bd"
|
|
1300
1539
|
}
|
|
1301
1540
|
];
|
|
1302
1541
|
export const CONTENT_GUIDES = [
|
|
@@ -1348,6 +1587,14 @@ export const CONTENT_GUIDES = [
|
|
|
1348
1587
|
"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",
|
|
1349
1588
|
"revision": "6799e74c42287dc5"
|
|
1350
1589
|
},
|
|
1590
|
+
{
|
|
1591
|
+
"id": "ads",
|
|
1592
|
+
"title": "Ads: pay to show more people what already worked",
|
|
1593
|
+
"summary": "Explains how Sprid thinks about paid ads on Instagram, Facebook, YouTube and TikTok: when to spend, how a promotion runs from draft to result, and which numbers to believe along the way.",
|
|
1594
|
+
"url": "https://sprid.studio/docs/ads",
|
|
1595
|
+
"markdown": "# Ads: pay to show more people what already worked\n\n**What this guide does:** Explains how Sprid thinks about paid ads on\nInstagram, Facebook, YouTube and TikTok: when to spend, how a promotion runs\nfrom draft to result, and which numbers to believe along the way.\n\nAds don't rescue a post nobody wanted. They amplify, and they amplify\nwhatever you give them, including a weak idea. So the order Sprid follows is\norganic first: publish, see which posts people actually watched and saved, and\nput money behind the one that already beat your normal. That post has passed\nthe hardest test for free.\n\n## What we believe\n\n**Promote proof, not hope.** A post that did twice your median has evidence\nbehind it. A brand-new creative has only an opinion. Start with the post, and\nmake new creatives once you know what the winning one had.\n\n**Nothing spends without you.** Everything Sprid or your agent prepares is\npaused. Exactly one step starts spending, and it waits for your yes, showing\nwhat will run, what it costs a day and for how long. An agent never moves a\nbudget between reads on its own.\n\n**Keep the words, vary the picture.** We read 3,549 live ads across five\nmarkets. Among advertisers running eight or more, the median was 2.9 ads per\nline of copy, and the largest wellness advertiser ran 109 ads on essentially one\nline. The ad library counts placements separately, so the exact ratio overstates,\nbut the shape holds: the people who keep paying hold the copy and change the\ncreative. A new version changes one thing, or the result teaches nothing.\n\n**Give it time to be readable.** Meta's delivery settles after roughly 50\noptimisation events in a week, and Google and TikTok need a similar learning\nperiod. Before that, a result is noise, and the most\ncommon way to waste money on ads is to stop an ad set before it has said\nanything, then decide the idea doesn't work. Sprid says whether a result is\nreadable before it says whether it worked.\n\n**Judge by what happens in your product.** Cost per install or subscriber comes\nfirst and impressions come last, because the order a report puts numbers in is\nthe order people believe them in. When your app reports who became a paying\ncustomer, that outranks anything the platform can see.\n\n## Two ways to start\n\n**Promote a post.** Pick a post already live, set a daily budget and an end\ndate, and confirm. The ad runs only where that post lives, through the network\nbehind it:\n\n| Post live on | Runs as | Paid by |\n|---|---|---|\n| Instagram | a Meta ad on Instagram only | your Meta ad account |\n| Facebook | a Meta ad on Facebook only | your Meta ad account |\n| YouTube | a Google Ads video campaign (YouTube, Shorts, Discover, Gmail) | your Google Ads account |\n| TikTok | a Spark Ad on TikTok | your TikTok ad account |\n\nSprid checks the ad account can pay before you choose a budget, the network\nreviews the ad, and you get an email when it goes live, is rejected (with the\nnetwork's reason) or ends (with what the money bought). The budget and end date\ncan change while it runs, from Sprid. A TikTok post also needs the creator's\npermission for that one post: a code the TikTok app makes under the post's Ad\nsettings, pasted into Sprid.\n\n**Run a test.** For finding out what to say, not just saying it louder. Tests\nrun on Meta. Every\ncreative is built from a named shape, such as the screen that shows the app's\nverdict about the reader, two columns where the reader does the arithmetic, or\none sourced number. The shape is what makes a result transferable: \"showing the\nverdict beat listing features\" is something you can build again, \"ad 7 beat\nad 3\" is not.\n\n## The checks before money moves\n\n- **The copy check.** Banned words, your own never-say list, invented urgency,\n and openings that name an identity (\"for anxious people\") instead of a moment\n (\"you reread the message four times\"). Warnings pass, errors block the launch.\n- **Regulated categories.** Housing, credit, employment and political ads must\n be declared. An undeclared one isn't quietly rejected, it's pulled with the\n ad account attached. Anything about where people live, what they can borrow or\n who gets hired counts, even when the product doesn't look like it.\n- **The payment method**, before a budget is chosen rather than after Meta has\n accepted the campaign and refused the ad.\n\n## Reading the result\n\nAsk for an ad set's verdict once a week and it answers three questions: did it\nwork, why, and what next.\nWhen a test ends, it writes down what the hypothesis was, what happened and\nwhich shape carried it, so the next test starts from that instead of from zero.\nA losing set is data: close it and say what it ruled out.\n\nTwo rules for the numbers. A campaign's totals and its ad sets' totals are\nseparate readings of the same money, never added together. And a period that\nends today and one that ended last week are two different readings, so every\nnumber says which window it covers.\n\n## What competitors can and can't tell you\n\nMeta's Ad Library shows every active ad: the creative, the copy and the date it\nstarted. It shows no spend, no clicks and no results. Days live means somebody\nkeeps paying, not that it works, so a competitor's ad is \"longest-running\",\nnever \"best-performing\". Sprid's scout research reads the library for your own\nmarket, which is where the shapes above came from.\n\n## You need\n\n- An ad account with a card on it, connected to Sprid, for the network behind\n the post: [Meta Ads](https://sprid.studio/docs/connect/meta-ads) for\n Instagram and Facebook, [Google Ads](https://sprid.studio/docs/connect/google-ads)\n for YouTube, [TikTok Ads](https://sprid.studio/docs/connect/tiktok-ads) for\n TikTok.\n- A published post to promote, or, on Meta, an image or video to build a\n creative from.\n- For an agent to launch anything, a token that holds the `ads:spend`\n permission. It's granted deliberately, on a token made for it.\n",
|
|
1596
|
+
"revision": "a6578de65ac0525b"
|
|
1597
|
+
},
|
|
1351
1598
|
{
|
|
1352
1599
|
"id": "reel-first-seconds",
|
|
1353
1600
|
"title": "The first seconds of a reel",
|