@sprid/cli 1.2.0 → 1.2.1
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 +4 -0
- package/README-post.md +4 -4
- package/package.json +1 -1
- package/src/commands/post-video.mjs +3 -3
- package/src/commands/status.mjs +1 -1
- package/src/docs/commands.mjs +4 -4
- package/src/docs/guides.generated.mjs +7 -7
- package/src/post/cli.mjs +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
sprid follows semver: a breaking change to a command's arguments, its output
|
|
4
4
|
shape or its exit codes is a major release.
|
|
5
5
|
|
|
6
|
+
## 1.2.1 - 2026-10-07
|
|
7
|
+
|
|
8
|
+
- Output and command help say a post is scheduled, never booked: `queue cancel` now reports “unscheduled”.
|
|
9
|
+
|
|
6
10
|
## 1.2.0 - 2026-10-07
|
|
7
11
|
|
|
8
12
|
- Pinterest account-wide analytics and top Pins, board descriptions and section management.
|
package/README-post.md
CHANGED
|
@@ -542,7 +542,7 @@ part reports a skip. `sync` re-sends a slice that changed after the draft, and
|
|
|
542
542
|
|
|
543
543
|
## `sync`: a re-render, into posts that already exist
|
|
544
544
|
|
|
545
|
-
**A
|
|
545
|
+
**A scheduled post is never edited. The source is re-rendered and the queue catches
|
|
546
546
|
up.** That is the design decision, and the alternative is worse than it sounds:
|
|
547
547
|
reaching into Sprid to swap a file on a scheduled post makes the queue the
|
|
548
548
|
source of truth for content, and then a spec and a published post can disagree
|
|
@@ -568,7 +568,7 @@ its slot. Three rules, two of them refusals:
|
|
|
568
568
|
- **It does the minimum.** Media changed, caption changed, or neither. A slug
|
|
569
569
|
whose caption moved does not get its video re-uploaded.
|
|
570
570
|
|
|
571
|
-
Changing media across a
|
|
571
|
+
Changing media across a scheduled queue requires reconciling both uploaded media and post metadata.
|
|
572
572
|
`sync` keeps that operation reproducible.
|
|
573
573
|
|
|
574
574
|
**`mediaShaAtPush` is what makes it possible**, and it is the counterpart of
|
|
@@ -612,7 +612,7 @@ holding your posts; `doctor` is the command that can tell you which.
|
|
|
612
612
|
**The queue lives in Sprid, and until `pull` nothing local could read it.** A
|
|
613
613
|
post is committed; when it publishes was not. So the repo could not answer "what
|
|
614
614
|
goes out tomorrow" or "which one went out last" without the API being up, and a
|
|
615
|
-
|
|
615
|
+
schedule made weeks ago left no trace anybody could review in a diff.
|
|
616
616
|
|
|
617
617
|
`pull` writes each post's status and dates into `sprid.remote` on its manifest,
|
|
618
618
|
which is what lets `status` print a calendar and work offline.
|
|
@@ -661,7 +661,7 @@ instagram` records a post's selection. With no selection, the existing Instagram
|
|
|
661
661
|
TikTok and YouTube checks remain the default. This selects validation targets;
|
|
662
662
|
publishing destinations are still chosen when scheduling in Sprid. Local projection
|
|
663
663
|
rules cover Instagram, TikTok, YouTube and Pinterest; other platforms report a
|
|
664
|
-
skip, and `--deep` reads the
|
|
664
|
+
skip, and `--deep` reads the scheduled post’s server projection. Media checks retain
|
|
665
665
|
their existing defaults and can be customized with `lane.expect`.
|
|
666
666
|
|
|
667
667
|
`sprid post <command> --json` writes one JSON result to stdout and diagnostics to
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sprid/cli",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "The Sprid command line: connect your app, review results, prepare posts and store screenshots, and release mobile apps.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
|
@@ -96,7 +96,7 @@ async function render(ctx, post) {
|
|
|
96
96
|
if (ctx.json) ctx.out({ quote, render: quote.existingRender, reused: true });
|
|
97
97
|
else {
|
|
98
98
|
ctx.print(` ${C.ok(OK)} Already rendered at this version, nothing to pay: ${renderLine(quote.existingRender)}`);
|
|
99
|
-
ctx.print(C.dim(`
|
|
99
|
+
ctx.print(C.dim(` Schedule it: sprid post schedule ${post} --video-render ${quote.existingRender.id} …`));
|
|
100
100
|
}
|
|
101
101
|
return 0;
|
|
102
102
|
}
|
|
@@ -138,7 +138,7 @@ async function render(ctx, post) {
|
|
|
138
138
|
}
|
|
139
139
|
ctx.print(` ${C.ok(OK)} ${res.reused ? "Reused" : "Started"} ${renderLine(res)}`);
|
|
140
140
|
ctx.print(C.dim(` Follow it: sprid post video ${post}`));
|
|
141
|
-
if (mode === "final") ctx.print(C.dim(`
|
|
142
|
-
else ctx.print(C.dim(" A preview is for review.
|
|
141
|
+
if (mode === "final") ctx.print(C.dim(` Schedule it: sprid post schedule ${post} --video-render ${res.id} …`));
|
|
142
|
+
else ctx.print(C.dim(" A preview is for review. Scheduled posts publish a --final render."));
|
|
143
143
|
return 0;
|
|
144
144
|
}
|
package/src/commands/status.mjs
CHANGED
|
@@ -198,7 +198,7 @@ export async function queue(ctx) {
|
|
|
198
198
|
const id = needId(arg, "sprid queue cancel <publishId>");
|
|
199
199
|
await ctx.api().del(`/api/publishes/${id}`);
|
|
200
200
|
if (ctx.json) ctx.out({ ok: true, publishId: id });
|
|
201
|
-
else ctx.print(` ${C.ok(OK)}
|
|
201
|
+
else ctx.print(` ${C.ok(OK)} ${id} unscheduled. The post stays as it is.`);
|
|
202
202
|
return 0;
|
|
203
203
|
}
|
|
204
204
|
if (sub) throw usage("sprid queue [retry|cancel <publishId>]");
|
package/src/docs/commands.mjs
CHANGED
|
@@ -34,7 +34,7 @@ export const COMMAND_GROUPS = [
|
|
|
34
34
|
['post', 'sprid post preview --id <postId>', 'Preview a shared post. Without --id, historical local preview arguments are unchanged.'],
|
|
35
35
|
['post', 'sprid post deliveries <postId>', 'Read scheduled, failed and delivered publications with platform receipts.'],
|
|
36
36
|
['post', 'sprid post video <postId>', 'Read the post\'s own video, compiled from its slides: its revision and hash, anything that stops it rendering, each slide\'s start, end and whether its length is pinned, which outputs are on, and the renders of this exact version. Edit it with sprid tool call edit_post_video.'],
|
|
37
|
-
['post', 'sprid post video render <postId> [--confirm] [--ceiling N] [--final]', 'Quote a render of the video as it is now, then render it with --confirm, spending at most --ceiling credits (default 30). A render of the same version is returned without charge.
|
|
37
|
+
['post', 'sprid post video render <postId> [--confirm] [--ceiling N] [--final]', 'Quote a render of the video as it is now, then render it with --confirm, spending at most --ceiling credits (default 30). A render of the same version is returned without charge. Scheduled posts publish a --final render; pass its id to sprid post schedule --video-render.'],
|
|
38
38
|
['post', 'sprid post handoff <postId>', 'Print the prompt to paste into an agent: what the post currently is, which verbs to call, and an empty line for the ask. Same string the editor\'s "Hand off to agent" button copies.'],
|
|
39
39
|
] },
|
|
40
40
|
{ title: 'Installation and updates', entries: [
|
|
@@ -106,9 +106,9 @@ export const COMMAND_GROUPS = [
|
|
|
106
106
|
['marketing-review', 'sprid marketing-review query --app <slug> --query-file <query.sql>', 'Run a read-only PostHog query through the app’s stored connection.'],
|
|
107
107
|
['queue', 'sprid queue', 'Show what is scheduled and what failed.'],
|
|
108
108
|
['queue', 'sprid queue retry <publishId>', 'Retry a failed or missed publish.'],
|
|
109
|
-
['queue', 'sprid queue cancel <publishId>', '
|
|
110
|
-
['post', 'sprid post schedule <postId> (--at ISO|--next|--queue-end) [--platforms a,b] [--time HH:MM] [--shift-later] [--options <options.json>] [--video-render <renderId>]', '
|
|
111
|
-
['post', 'sprid post schedule-batch --account <slug> --ids SPR-9,SPR-10 [--cadence daily|every-other-day|weekdays] [--times 09:00] [--start YYYY-MM-DD] [--platforms a,b] [--skip-busy] [--dry-run]', 'Place a list of posts on a cadence in one call. --dry-run returns the plan without
|
|
109
|
+
['queue', 'sprid queue cancel <publishId>', 'Unschedule one destination. The post itself is unchanged and can be scheduled again.'],
|
|
110
|
+
['post', 'sprid post schedule <postId> (--at ISO|--next|--queue-end) [--platforms a,b] [--time HH:MM] [--shift-later] [--options <options.json>] [--video-render <renderId>]', 'Schedule a post: at a time, the next free slot, or after everything already scheduled. --shift-later moves later scheduled posts down a slot. A destination that goes out as the post\'s video needs --video-render, a final render of the video as it is now. TikTok goes as a draft to the TikTok inbox: {"tiktok": {"postMode": "MEDIA_UPLOAD"}} in --options. A direct TikTok post is answered and confirmed by the person on the review link the refusal prints. A Pinterest Pin needs its board and destination link saved first with sprid pinterest set.'],
|
|
111
|
+
['post', 'sprid post schedule-batch --account <slug> --ids SPR-9,SPR-10 [--cadence daily|every-other-day|weekdays] [--times 09:00] [--start YYYY-MM-DD] [--platforms a,b] [--skip-busy] [--dry-run]', 'Place a list of posts on a cadence in one call. --dry-run returns the plan without scheduling anything.'],
|
|
112
112
|
['post', 'sprid post publish <postId> [--platforms a,b] [--options <options.json>] [--video-render <renderId>] [--yes]', 'Publish now. Public and not reversible, so it asks unless --yes. Read the receipts with sprid post deliveries.'],
|
|
113
113
|
['inbox', 'sprid inbox [--view needs_you|all|done] [--kind all|comment|review] [--accounts a,b] [--app <slug>] [--intent <intent>] [--limit N]', 'Comments on posts and ads plus store reviews, as one ranked queue.'],
|
|
114
114
|
['inbox', 'sprid inbox draft <commentId> [--count N]', 'Draft replies in the account’s reply voice. Nothing is sent.'],
|
|
@@ -1080,7 +1080,7 @@ export const GUIDES = [
|
|
|
1080
1080
|
"id": "before-publishing",
|
|
1081
1081
|
"title": "Before publishing",
|
|
1082
1082
|
"kind": "detail",
|
|
1083
|
-
"markdown": "TikTok requires you to choose these on every post, with nothing pre-filled:\n\n- Audience (privacy) and whether to allow comments. Videos also offer Duet and Stitch where your account allows them.\n- The commercial-content disclosure, if the post promotes your business or another brand. Branded content cannot use **Only me**.\n- The music and branded-content terms shown above the publish button.\n\nIn a batch, your choices apply to every post shown. Rescheduling a
|
|
1083
|
+
"markdown": "TikTok requires you to choose these on every post, with nothing pre-filled:\n\n- Audience (privacy) and whether to allow comments. Videos also offer Duet and Stitch where your account allows them.\n- The commercial-content disclosure, if the post promotes your business or another brand. Branded content cannot use **Only me**.\n- The music and branded-content terms shown above the publish button.\n\nIn a batch, your choices apply to every post shown. Rescheduling a post keeps its original choices."
|
|
1084
1084
|
},
|
|
1085
1085
|
{
|
|
1086
1086
|
"id": "if-it-fails",
|
|
@@ -1101,8 +1101,8 @@ export const GUIDES = [
|
|
|
1101
1101
|
"markdown": "- [Content Posting API](https://developers.tiktok.com/doc/content-posting-api-get-started)\n- [Direct Post reference](https://developers.tiktok.com/doc/content-posting-api-reference-direct-post)\n- [Query creator info](https://developers.tiktok.com/doc/content-posting-api-reference-query-creator-info)\n- [Content Sharing Guidelines](https://developers.tiktok.com/doc/content-sharing-guidelines)\n- [Login Kit for web](https://developers.tiktok.com/doc/login-kit-web)"
|
|
1102
1102
|
}
|
|
1103
1103
|
],
|
|
1104
|
-
"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
|
|
1105
|
-
"revision": "
|
|
1104
|
+
"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 post 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",
|
|
1105
|
+
"revision": "c8c6bf6eafb18eda"
|
|
1106
1106
|
},
|
|
1107
1107
|
{
|
|
1108
1108
|
"id": "tiktok-comments",
|
|
@@ -1665,8 +1665,8 @@ export const CONTENT_GUIDES = [
|
|
|
1665
1665
|
"title": "Use Sprid in chat",
|
|
1666
1666
|
"summary": "Take an app from setup to a reviewed post and confirmed delivery using only your chat app and a browser. No CLI or installed skills needed.",
|
|
1667
1667
|
"url": "https://sprid.studio/docs/chat",
|
|
1668
|
-
"markdown": "# Use Sprid in chat\n\n**What this guide does:** Take an app from setup to a reviewed post and confirmed delivery using only your chat app and a browser. No CLI or installed skills needed.\n\nYou tell the agent what you want; it handles the tool calls below. The tool names are here so the agent knows the order. Setup should only ask for the connection the current task needs.\n\n## Connect Sprid\n\nChoose your chat at `https://sprid.studio/start#chat` and follow its steps. Your chat may call this an app, connector or integration. The address is `https://api.sprid.studio/api/mcp?surface=core`. Sign in to Sprid and authorize the right workspace.\n\nAgent: when available, start with `get_marketing_plan` and reuse its prepared work and next step rather than starting over. Discovery can start from a website or store URL, with no social account.\n\n## Pick the app and account\n\nAgent: call `get_capabilities`, then `list_apps` and `list_accounts`, and reuse what exists. If the app is missing, get its description or website/store URL, confirm it and create it with `upsert_app_profile`. A content account owns voice and media; a channel is one exact social destination. Never pick a handle just because it is first in a list. A missing account is created in the dashboard.\n\n## Connect a destination or data source\n\n- **Social channel:** `get_documentation` with the platform name, then `connect_channel`. The person opens the returned link and approves in the browser; `channel_status` confirms the saved result.\n- **Store or analytics:** the person enters identifiers and the key in the secure form at `https://app.sprid.studio/settings/app-profiles`. Credentials never go in chat, tool arguments or reports. A saved-key indicator only proves configuration; `get_marketing_review` does a live read.\n\n## Bring images or a finished video\n\nDraft copy in the chat, in the account’s voice. No extra model key needed.\n\nIf the chat exposes the attached files, pass them to `import_assets`: each file has `download_url` and `file_id`, optionally `mime_type` and `file_name`. Public HTTPS URLs and existing `{kind, id}` library references also work. Limits: PNG, JPEG, WebP or finished MP4, up to 32 MiB each. Each position reports on its own; retry the missing ones before building the post. Files are copied to the account and deduplicated by bytes; temporary download URLs are not kept.\n\nA `sandbox:` URL or local path can’t be fetched. For an image the chat generated, use the existing file if the host exposes it (not verified for every host); otherwise download it and use the browser upload. Never ask the agent to recreate an image or transcribe binary data.\n\n**Fallback: browser upload.** Call `create_upload_session` with the account and a new UUID `requestId`. The person opens the link and picks the files. Then read `get_studio_job` with the returned ID; its result keys are zero-based positions. The same session and asset IDs work from any other chat. No image-generation charge.\n\n## Create and review the post\n\nCall `create_post` with a new UUID `requestId`, the account, ordered `{kind, id}` assets, captions and aspect ratio. Reuse the same ID and inputs if a response is lost.\n\n- `presentation: \"finished\"` keeps artwork as-is: no text overlay, no added music. Images fit inside the canvas, which can add margins.\n- `presentation: \"editable\"` allows `text` and `subtitle` on image slides.\n- One finished video becomes one reel. Never mix a video with image slides.\n\nEdit with `update_post` (captions) and `update_slides` (slides). Call `preview_post` and check every slide or the video: captions, order, crop, legibility. It returns a small image of each slide for the agent to look at and an embedded preview for the person; a chat without embedded apps still shows the images. A preview is not approval to publish.\n\nAgent: show the person the preview, and link every post you mention with the `links` the tools return, as a Markdown link on its ref or title: `links.review` when the ask is to approve or schedule, `links.editor` when it is to change something. Never draw the slides in your own widget or artifact: those sandboxes block Sprid's media host and the images show broken.\n\n## Make a reel from photos or text\n\nCheck `get_capabilities` for hosted creation first. `create_reel` takes ordered scenes (`imageId`, `text`, `durationMs`), up to 120 seconds, and makes a silent MP4. It costs 3 Sprid credits per started render minute, or uses the plan allowance: check `spend_status`, tell the person, and get their go-ahead before sending `confirmed: true`. Save the job ID and read `get_studio_job`. Retrying with the same ID never starts a second render; a stopped job reports `needs_attention` instead of charging again.\n\nTo turn an existing post's slides into a video instead, use its own video: `get_video_capabilities`, then `get_post_video` and `edit_post_video` for transitions and motion, and `update_post {outputs}` for which destinations get it. Rendering it is metered the same way: `quote_post_video_render`, the person's go-ahead, then `render_post_video` with `confirmed: true`.
|
|
1669
|
-
"revision": "
|
|
1668
|
+
"markdown": "# Use Sprid in chat\n\n**What this guide does:** Take an app from setup to a reviewed post and confirmed delivery using only your chat app and a browser. No CLI or installed skills needed.\n\nYou tell the agent what you want; it handles the tool calls below. The tool names are here so the agent knows the order. Setup should only ask for the connection the current task needs.\n\n## Connect Sprid\n\nChoose your chat at `https://sprid.studio/start#chat` and follow its steps. Your chat may call this an app, connector or integration. The address is `https://api.sprid.studio/api/mcp?surface=core`. Sign in to Sprid and authorize the right workspace.\n\nAgent: when available, start with `get_marketing_plan` and reuse its prepared work and next step rather than starting over. Discovery can start from a website or store URL, with no social account.\n\n## Pick the app and account\n\nAgent: call `get_capabilities`, then `list_apps` and `list_accounts`, and reuse what exists. If the app is missing, get its description or website/store URL, confirm it and create it with `upsert_app_profile`. A content account owns voice and media; a channel is one exact social destination. Never pick a handle just because it is first in a list. A missing account is created in the dashboard.\n\n## Connect a destination or data source\n\n- **Social channel:** `get_documentation` with the platform name, then `connect_channel`. The person opens the returned link and approves in the browser; `channel_status` confirms the saved result.\n- **Store or analytics:** the person enters identifiers and the key in the secure form at `https://app.sprid.studio/settings/app-profiles`. Credentials never go in chat, tool arguments or reports. A saved-key indicator only proves configuration; `get_marketing_review` does a live read.\n\n## Bring images or a finished video\n\nDraft copy in the chat, in the account’s voice. No extra model key needed.\n\nIf the chat exposes the attached files, pass them to `import_assets`: each file has `download_url` and `file_id`, optionally `mime_type` and `file_name`. Public HTTPS URLs and existing `{kind, id}` library references also work. Limits: PNG, JPEG, WebP or finished MP4, up to 32 MiB each. Each position reports on its own; retry the missing ones before building the post. Files are copied to the account and deduplicated by bytes; temporary download URLs are not kept.\n\nA `sandbox:` URL or local path can’t be fetched. For an image the chat generated, use the existing file if the host exposes it (not verified for every host); otherwise download it and use the browser upload. Never ask the agent to recreate an image or transcribe binary data.\n\n**Fallback: browser upload.** Call `create_upload_session` with the account and a new UUID `requestId`. The person opens the link and picks the files. Then read `get_studio_job` with the returned ID; its result keys are zero-based positions. The same session and asset IDs work from any other chat. No image-generation charge.\n\n## Create and review the post\n\nCall `create_post` with a new UUID `requestId`, the account, ordered `{kind, id}` assets, captions and aspect ratio. Reuse the same ID and inputs if a response is lost.\n\n- `presentation: \"finished\"` keeps artwork as-is: no text overlay, no added music. Images fit inside the canvas, which can add margins.\n- `presentation: \"editable\"` allows `text` and `subtitle` on image slides.\n- One finished video becomes one reel. Never mix a video with image slides.\n\nEdit with `update_post` (captions) and `update_slides` (slides). Call `preview_post` and check every slide or the video: captions, order, crop, legibility. It returns a small image of each slide for the agent to look at and an embedded preview for the person; a chat without embedded apps still shows the images. A preview is not approval to publish.\n\nAgent: show the person the preview, and link every post you mention with the `links` the tools return, as a Markdown link on its ref or title: `links.review` when the ask is to approve or schedule, `links.editor` when it is to change something. Never draw the slides in your own widget or artifact: those sandboxes block Sprid's media host and the images show broken.\n\n## Make a reel from photos or text\n\nCheck `get_capabilities` for hosted creation first. `create_reel` takes ordered scenes (`imageId`, `text`, `durationMs`), up to 120 seconds, and makes a silent MP4. It costs 3 Sprid credits per started render minute, or uses the plan allowance: check `spend_status`, tell the person, and get their go-ahead before sending `confirmed: true`. Save the job ID and read `get_studio_job`. Retrying with the same ID never starts a second render; a stopped job reports `needs_attention` instead of charging again.\n\nTo turn an existing post's slides into a video instead, use its own video: `get_video_capabilities`, then `get_post_video` and `edit_post_video` for transitions and motion, and `update_post {outputs}` for which destinations get it. Rendering it is metered the same way: `quote_post_video_render`, the person's go-ahead, then `render_post_video` with `confirmed: true`. Schedule a video destination with the render's `videoRenderId`.\n\nApp captures, simulator recordings and your own build scripts run outside remote MCP. Upload their finished files.\n\n## Approve and confirm delivery\n\nPublishing and scheduling require the person's approval of the preview and intended destinations. The browser review link remains available for picking channels, time and platform options.\n\nTikTok requires an explicit `postMode`; nothing defaults to a direct post or a draft:\n\n- `MEDIA_UPLOAD` sends the media to the creator's TikTok inbox. They finish the post in TikTok; report it as a draft.\n- `DIRECT_POST` requires `privacyLevel`, `disableComment`, `disableDuet`, `disableStitch`, `yourBrandToggle`, `brandedContentToggle` and `confirmed: true` in `options.tiktok`. Fetch fresh choices with `list_connections {includeTikTokCreatorInfo: true}` and show the creator identity, media and caption. The person chooses every setting for this post. Photo posts use `disableDuet: true` and `disableStitch: true`; comments respect the creator's restriction. Branded content cannot use `SELF_ONLY`.\n\nBefore sending `confirmed: true`, get the person's approval of this exact post and their agreement to TikTok's [Music Usage Confirmation](https://www.tiktok.com/legal/page/global/music-usage-confirmation/en), plus the [Branded Content Policy](https://www.tiktok.com/legal/page/global/bc-policy/en) for branded content. That flag is a caller's attestation, not proof of a click in TikTok's prescribed UI. Never invent choices or carry usual preferences into a new post. If choices are missing, open the returned `approvalUrl` for the person to answer in Sprid.\n\nAn approval covers the version the person saw. If you edit a scheduled direct post afterwards, the edit's result carries `tiktokApproval`: the scheduled post will not publish as it is. Show the person the change and the settings in its `rebook` call, ask whether to post this version, and only after they agree call `rebook` with `options.tiktok.confirmed: true` added. `post_queue` returns the same call while the approval is outstanding.\n\nTikTok's published guidelines specify a privacy dropdown and posting controls. Acceptance of this chat-based confirmation flow has not been verified with TikTok. Keep that distinction explicit in review submissions.\n\nAfter approval, read `post_queue`. Keep draft, scheduled, failed and delivered apart, and report delivery only with the platform receipt. Read a failure before retrying. Never repeat a publish call just because a response was lost.\n\nFor results, read `get_documentation` for `marketing-review`. Keep the same app, account and post IDs across clients. `next_actions` gives the next step; an empty `verb` means follow its browser link.\n\n## Limits and troubleshooting\n\n- **Attachment can’t be read:** use the browser upload, not the CLI.\n- **Missing account or service:** set it up in the dashboard. Never paste credentials into chat.\n- **Hosted creation:** check `get_capabilities` before promising it. Store screenshots and store releases are local unless it reports otherwise.\n- **File transfer:** whether a host exposes uploaded or generated files has to be checked per host. A supported schema doesn’t prove it.\n",
|
|
1669
|
+
"revision": "bcf0b60a52e82938"
|
|
1670
1670
|
},
|
|
1671
1671
|
{
|
|
1672
1672
|
"id": "local",
|
|
@@ -1697,8 +1697,8 @@ export const CONTENT_GUIDES = [
|
|
|
1697
1697
|
"title": "Pinterest content and publishing",
|
|
1698
1698
|
"summary": "Plan searchable Pins, approve a useful batch, let Sprid publish the selected Pins on schedule and judge the result by qualified traffic and activation.",
|
|
1699
1699
|
"url": "https://sprid.studio/docs/pinterest",
|
|
1700
|
-
"markdown": "# Pinterest content and publishing\n\n**What this guide does:** Plan searchable Pins, approve a useful batch, let Sprid publish the selected Pins on schedule and judge the result by qualified traffic and activation.\n\nChecked against Pinterest’s guidance on 21 September 2026. Anything labelled a test is a starting hypothesis, not a claim about what will win for your account.\n\n## How Pinterest discovery works\n\nPinterest is visual search and recommendation. A Pin can show up in search, home feeds and related Pins long after it is published, mostly regardless of followers.\n\nRelevance comes from the Pin itself and how people interact with it: clear keywords, original imagery, link quality and board context. Pinterest publishes no weighting. Make the visual, title, description, board and landing page all answer the same “what is this about?”, in the words a person would search, without repeating a keyword unnaturally.\n\nBecause Pins keep getting found, compare them at equal ages and keep older Pins in the analysis.\n\n## Start with the destination\n\nPick the page and the action before making the visual:\n\n1. One useful guide, tool, feature page or store destination.\n2. The concrete question it answers.\n3. One next action that continues the same task, such as opening that feature.\n4. The plain destination URL. When it publishes, Sprid adds `utm_source=pinterest` and the Pin’s publish ID, so visits trace back to the exact Pin. A link that already carries its own `utm_source` goes out as written and loses that per-Pin tagging; add your own only for your own campaign names.\n\nThe page must deliver the pictured promise immediately and work on a phone. A specific Pin that lands on a generic homepage breaks the promise. A store visit is not an install, and an outbound click is not activation: report each step separately.\n\n## Prepare the website\n\n- **Claim your website** in Pinterest to link the profile to Pins from that domain and get website analytics. A website can be claimed by only one Pinterest account.\n- **Rich Pins:** for articles, products or recipes, add Open Graph or Schema.org metadata. Pinterest syncs it from the page; it is not a Sprid export. Check how it appears before a batch, because editing a Pin by hand can override synced article or recipe details.\n- Make sure Pinterestbot can read the page, links work and it loads fast on mobile.\n\n## Choose topics and boards\n\nBuild the topic list from Pinterest Trends, Pinterest search suggestions, customer language and site queries that already work. Search Console is a useful seed, but Google demand doesn’t prove Pinterest demand.\n\nMix evergreen questions with relevant seasonal moments. Use focused boards whose name and description explain the topic: a Pin about apartment-viewing costs belongs on a board about buying an apartment, not a broad “Ideas” board. Only create a board when the topic can fill it.\n\n## Build the Pin\n\n| | Ratio | Ideal size | Notes |\n|---|---|---|---|\n| Image | 2:3 | 1000 × 1500 px | |\n| Video | 9:16 | 1080 × 1920 px | 4 seconds to 5 minutes, H.264 or H.265 |\n\nSprid publishes one image or one finished video per Pin. It never turns an Instagram carousel into a Pin, so make a dedicated asset and check its crop in the preview.\n\nAt thumbnail size the subject and benefit should still be clear: one focus, strong contrast, short readable overlay copy. Check the real phone preview. The Pin must stand alone; an opener that only makes sense after swiping is incomplete here.\n\nFormats worth testing:\n\n- an editorial cover for a specific guide\n- a reference card with a concrete checklist or answer\n- a real app demo showing one task\n- a coherent visual collection for design, travel or architecture\n- a short captioned video when motion explains the task better\n\nPinterest can label detected or declared AI imagery. Keep provenance, use accurate licensed assets and disclose where required. Never publish an image that misrepresents the linked place, product or result.\n\n## Write the metadata\n\n| Field | Limit | Notes |\n|---|---|---|\n| Title | 100 characters | Put the subject early; feeds may cut it. |\n| Description | 800 characters | Often hidden in feeds, but Pinterest reads it for relevance. |\n| Alt text | 500 characters | Describe what the image shows. Not a keyword field. |\n\nWrite a natural title and description naming the topic, audience or situation and what the destination provides. Choose a relevant board, add a working destination, and set the AI disclosure in Sprid when it applies. No stuffed keyword variants, unrelated trends or promises the page doesn’t keep.\n\nThe visual earns attention, the metadata gives context, the destination completes the task. Keep all three aligned.\n\n## Publish your first Pin\n\n1. Connect Pinterest, run `sprid status` and confirm the account.\n2. Create or open a post with exactly one image (ideally 2:3) or one finished video (ideally 9:16).\n3. Open **Publish**, select **Pinterest**, then pick a **Board** and optional section.\n4. Add the title, description, destination, alt text and any AI disclosure.\n5. Check the rendered creative and every public field. Publish now or pick a time.\n6. After delivery, open the Pin signed out or from another account and follow its link.\n\n**CLI:** `sprid pinterest boards --account <slug>` lists board IDs, or says there are none; create one with `sprid pinterest create --account <slug> --name <name> [--privacy public|secret]`. `sprid pinterest set <postId> --board <id> --link <url>` saves the draft (optional metadata flags in `sprid docs cli`); review and schedule it in Sprid. `sprid pinterest metrics <publishId> --account <slug>` reads results.\n\n**MCP:** read `get_documentation` topic `pinterest-content`, pick the exact channel from `list_connections`, then `list_pinterest_boards`. If there are none, say so, and call `create_pinterest_board` only with a name and privacy the user approved. Save `pinterestOptions` with `update_post`, preferring `aspectRatio: \"2:3\"` for a new image Pin. Show the user the rendered Pin or dry-run batch, then call `schedule_post` or commit `schedule_batch` only for the Pins they chose. `get_pinterest_metrics` reads results.\n\n## Choose a cadence to test\n\nStart with **2 original Pins a day** in separate slots. With a broad catalog of useful destinations and genuinely distinct assets, test **5 a day** against that. These are Sprid’s proposed tests, not Pinterest limits or proven optimums. A small destination library needs more useful content, not cosmetic duplicates in the queue.\n\nRotate destinations and topics. Start with two distinct treatments per destination, on different days. No exact spacing or best hour is established.\n\nWhat the evidence says: Pinterest recommends original content at least weekly. [Sarah Hanford](https://www.sarahhanford.com/blog/pinterest-growth-organically-60-days) went from three Pins a day to five alongside keyword research and a new website, so frequency isn’t isolated as the cause. [Tailwind’s benchmark](https://www.tailwindapp.com/pinterest-marketing/research/2025-benchmark-study-part-1) of about 1.2 million organic Pins (2024 data) found results concentrated in a small share of Pins; its English-speaking, observational sample can’t set a universal cadence.\n\n## Review and schedule the batch\n\nFor a two-a-day test, choose **Daily** and two times in the batch scheduler, for example **09:00 and 17:00** (convenient, not researched best times). Check the account’s timezone and the actual dates before confirming; they may differ from your device. Keep a cadence the user already chose unless they ask to change it.\n\nOrder Pins so destinations alternate, and check the calendar preview for busy days, gaps and unplaced Pins. Preparing a large batch at once is fine; publication spreads across the schedule.\n\n**MCP:** `schedule_batch` with `cadence: \"daily\"`, `times: [\"09:00\", \"17:00\"]`, the account/channel and the selected post IDs. Run `dryRun: true` first and check `placements`, `unplaced` and `timezone`. Busy days are skipped by default, so read the returned plan instead of promising an end date. Commit only the reviewed Pins and report what was booked. CLI users read this guide with `sprid docs pinterest-content` and the queue with `sprid queue`; place batches in the scheduling screen or over MCP.\n\nBefore approving, check every Pin’s creative, metadata, destination, board and time, plus factual claims, rights and disclosure. Pinterest’s developer guidelines require the user to choose each Pin that publishes: Sprid shows the concrete Pins and schedule and records the selection. Those Pins then publish on time without asking again.\n\n- Moving only the time keeps the reviewed Pin. Changing content, board or link means reviewing and rescheduling it; changed drafts can be blocked at publish until reviewed.\n- After an unclear publish failure, check Pinterest before retrying; the Pin may already exist.\n- After the first Pin publishes, check it and its link from outside the connected account. A draft, preview or accepted schedule is not delivery.\n\n## Measure what happened\n\n- **Impressions:** times the Pin was on screen.\n- **Saves:** times people saved it to a board.\n- **Pin clicks:** opens of the Pin in close-up.\n- **Outbound clicks:** clicks through to a destination outside Pinterest.\n- **Video views:** at least 2 seconds with at least 50% of the video in view.\n\nEach is dated daily activity, kept as its own number. Pinterest reports no likes, comments or shares here, and Sprid does not collect Pin comments, so there is nothing for a Pin in the Inbox.\n\nFor app growth the path is **impression → outbound click → qualified visit → app action → activation**. Saves are a diagnostic, not the goal: a Pin with many saves and few outbound clicks may be a good reference on Pinterest and bring little acquisition.\n\nCompare Pins of similar age within the same topic, language and market. Judge creative by outbound-click rate and qualified visits, the whole path by activations per Pin and revenue where available. Pinterest’s outbound clicks and your own site sessions are measured differently; show both rather than forcing them to match.\n\nReview cohorts at 30, 60 and 90 days of Pin age. Report counts and coverage, including results with the top Pin removed, so one outlier can’t set the strategy. Keep a higher frequency while extra Pins keep adding qualified visits or activations; a lower click rate alone doesn’t cancel growth in total qualified traffic.\n\n- Visits but no activation: check page speed, message match and the next app action before making more Pins.\n- Low impressions: check topic relevance, board, metadata and visual clarity.\n- Negligible qualified traffic and activation across repeated relevant cohorts: stop scaling Pinterest for that app.\n\n## Improve the next batch\n\nChange one meaningful thing per comparison (visual treatment, question, format or destination angle) and repeat across comparable destinations. Organic distribution isn’t randomized, so call it a directional test, not an A/B test.\n\nMake fresh, useful treatments, not cosmetic duplicates. [Tailwind’s freshness study](https://www.tailwindapp.com/pinterest-marketing/research/2025-benchmark-study-part-three) found new images for an existing URL kept distribution better than reusing the same image and URL (an association, not a guaranteed lift). Pinterest advises against re-uploading the same Pin, and repetitive or irrelevant commercial content can be treated as spam. Space treatments of the same destination apart and keep every Pin on a relevant board.\n\nFor a worked example, [Elaine Timms’s recipe-client case](https://elainetimms.com/evergreen-growth-how-food-creator-pins-for-profit/) combines search-specific overlays, creative variants and email opt-ins. Its growth can’t be credited to cadence alone.\n\n## Sources\n\n- [How Pinterest discovery works](https://create.pinterest.com/blog/how-to-increase-discoverability-seo-pinterest/)\n- [Growing through search, boards and keywords](https://create.pinterest.com/blog/best-ways-to-grow-on-pinterest/)\n- [Pin specifications](https://help.pinterest.com/en/article/review-pin-specs)\n- [Pin performance and distribution](https://help.pinterest.com/en/business/article/pin-performance-and-distribution)\n- [Pinterest Analytics definitions](https://help.pinterest.com/en/business/article/pinterest-analytics)\n- [Pinterest developer guidelines](https://policy.pinterest.com/en/developer-guidelines)\n- [Pinterest API schema](https://github.com/pinterest/api-description/blob/main/v5/openapi.json)\n- [Pinterest community guidelines](https://policy.pinterest.com/en/community-guidelines)\n- [Pinterest labels for AI-generated or modified images](https://help.pinterest.com/en/article/gen-ai-labels)\n- [Claim your website](https://help.pinterest.com/en/business/article/claim-your-website)\n- [Rich Pins](https://help.pinterest.com/en-gb/business/article/rich-pins)\n",
|
|
1701
|
-
"revision": "
|
|
1700
|
+
"markdown": "# Pinterest content and publishing\n\n**What this guide does:** Plan searchable Pins, approve a useful batch, let Sprid publish the selected Pins on schedule and judge the result by qualified traffic and activation.\n\nChecked against Pinterest’s guidance on 21 September 2026. Anything labelled a test is a starting hypothesis, not a claim about what will win for your account.\n\n## How Pinterest discovery works\n\nPinterest is visual search and recommendation. A Pin can show up in search, home feeds and related Pins long after it is published, mostly regardless of followers.\n\nRelevance comes from the Pin itself and how people interact with it: clear keywords, original imagery, link quality and board context. Pinterest publishes no weighting. Make the visual, title, description, board and landing page all answer the same “what is this about?”, in the words a person would search, without repeating a keyword unnaturally.\n\nBecause Pins keep getting found, compare them at equal ages and keep older Pins in the analysis.\n\n## Start with the destination\n\nPick the page and the action before making the visual:\n\n1. One useful guide, tool, feature page or store destination.\n2. The concrete question it answers.\n3. One next action that continues the same task, such as opening that feature.\n4. The plain destination URL. When it publishes, Sprid adds `utm_source=pinterest` and the Pin’s publish ID, so visits trace back to the exact Pin. A link that already carries its own `utm_source` goes out as written and loses that per-Pin tagging; add your own only for your own campaign names.\n\nThe page must deliver the pictured promise immediately and work on a phone. A specific Pin that lands on a generic homepage breaks the promise. A store visit is not an install, and an outbound click is not activation: report each step separately.\n\n## Prepare the website\n\n- **Claim your website** in Pinterest to link the profile to Pins from that domain and get website analytics. A website can be claimed by only one Pinterest account.\n- **Rich Pins:** for articles, products or recipes, add Open Graph or Schema.org metadata. Pinterest syncs it from the page; it is not a Sprid export. Check how it appears before a batch, because editing a Pin by hand can override synced article or recipe details.\n- Make sure Pinterestbot can read the page, links work and it loads fast on mobile.\n\n## Choose topics and boards\n\nBuild the topic list from Pinterest Trends, Pinterest search suggestions, customer language and site queries that already work. Search Console is a useful seed, but Google demand doesn’t prove Pinterest demand.\n\nMix evergreen questions with relevant seasonal moments. Use focused boards whose name and description explain the topic: a Pin about apartment-viewing costs belongs on a board about buying an apartment, not a broad “Ideas” board. Only create a board when the topic can fill it.\n\n## Build the Pin\n\n| | Ratio | Ideal size | Notes |\n|---|---|---|---|\n| Image | 2:3 | 1000 × 1500 px | |\n| Video | 9:16 | 1080 × 1920 px | 4 seconds to 5 minutes, H.264 or H.265 |\n\nSprid publishes one image or one finished video per Pin. It never turns an Instagram carousel into a Pin, so make a dedicated asset and check its crop in the preview.\n\nAt thumbnail size the subject and benefit should still be clear: one focus, strong contrast, short readable overlay copy. Check the real phone preview. The Pin must stand alone; an opener that only makes sense after swiping is incomplete here.\n\nFormats worth testing:\n\n- an editorial cover for a specific guide\n- a reference card with a concrete checklist or answer\n- a real app demo showing one task\n- a coherent visual collection for design, travel or architecture\n- a short captioned video when motion explains the task better\n\nPinterest can label detected or declared AI imagery. Keep provenance, use accurate licensed assets and disclose where required. Never publish an image that misrepresents the linked place, product or result.\n\n## Write the metadata\n\n| Field | Limit | Notes |\n|---|---|---|\n| Title | 100 characters | Put the subject early; feeds may cut it. |\n| Description | 800 characters | Often hidden in feeds, but Pinterest reads it for relevance. |\n| Alt text | 500 characters | Describe what the image shows. Not a keyword field. |\n\nWrite a natural title and description naming the topic, audience or situation and what the destination provides. Choose a relevant board, add a working destination, and set the AI disclosure in Sprid when it applies. No stuffed keyword variants, unrelated trends or promises the page doesn’t keep.\n\nThe visual earns attention, the metadata gives context, the destination completes the task. Keep all three aligned.\n\n## Publish your first Pin\n\n1. Connect Pinterest, run `sprid status` and confirm the account.\n2. Create or open a post with exactly one image (ideally 2:3) or one finished video (ideally 9:16).\n3. Open **Publish**, select **Pinterest**, then pick a **Board** and optional section.\n4. Add the title, description, destination, alt text and any AI disclosure.\n5. Check the rendered creative and every public field. Publish now or pick a time.\n6. After delivery, open the Pin signed out or from another account and follow its link.\n\n**CLI:** `sprid pinterest boards --account <slug>` lists board IDs, or says there are none; create one with `sprid pinterest create --account <slug> --name <name> [--privacy public|secret]`. `sprid pinterest set <postId> --board <id> --link <url>` saves the draft (optional metadata flags in `sprid docs cli`); review and schedule it in Sprid. `sprid pinterest metrics <publishId> --account <slug>` reads results.\n\n**MCP:** read `get_documentation` topic `pinterest-content`, pick the exact channel from `list_connections`, then `list_pinterest_boards`. If there are none, say so, and call `create_pinterest_board` only with a name and privacy the user approved. Save `pinterestOptions` with `update_post`, preferring `aspectRatio: \"2:3\"` for a new image Pin. Show the user the rendered Pin or dry-run batch, then call `schedule_post` or commit `schedule_batch` only for the Pins they chose. `get_pinterest_metrics` reads results.\n\n## Choose a cadence to test\n\nStart with **2 original Pins a day** in separate slots. With a broad catalog of useful destinations and genuinely distinct assets, test **5 a day** against that. These are Sprid’s proposed tests, not Pinterest limits or proven optimums. A small destination library needs more useful content, not cosmetic duplicates in the queue.\n\nRotate destinations and topics. Start with two distinct treatments per destination, on different days. No exact spacing or best hour is established.\n\nWhat the evidence says: Pinterest recommends original content at least weekly. [Sarah Hanford](https://www.sarahhanford.com/blog/pinterest-growth-organically-60-days) went from three Pins a day to five alongside keyword research and a new website, so frequency isn’t isolated as the cause. [Tailwind’s benchmark](https://www.tailwindapp.com/pinterest-marketing/research/2025-benchmark-study-part-1) of about 1.2 million organic Pins (2024 data) found results concentrated in a small share of Pins; its English-speaking, observational sample can’t set a universal cadence.\n\n## Review and schedule the batch\n\nFor a two-a-day test, choose **Daily** and two times in the batch scheduler, for example **09:00 and 17:00** (convenient, not researched best times). Check the account’s timezone and the actual dates before confirming; they may differ from your device. Keep a cadence the user already chose unless they ask to change it.\n\nOrder Pins so destinations alternate, and check the calendar preview for busy days, gaps and unplaced Pins. Preparing a large batch at once is fine; publication spreads across the schedule.\n\n**MCP:** `schedule_batch` with `cadence: \"daily\"`, `times: [\"09:00\", \"17:00\"]`, the account/channel and the selected post IDs. Run `dryRun: true` first and check `placements`, `unplaced` and `timezone`. Busy days are skipped by default, so read the returned plan instead of promising an end date. Commit only the reviewed Pins and report what was scheduled. CLI users read this guide with `sprid docs pinterest-content` and the queue with `sprid queue`; place batches in the scheduling screen or over MCP.\n\nBefore approving, check every Pin’s creative, metadata, destination, board and time, plus factual claims, rights and disclosure. Pinterest’s developer guidelines require the user to choose each Pin that publishes: Sprid shows the concrete Pins and schedule and records the selection. Those Pins then publish on time without asking again.\n\n- Moving only the time keeps the reviewed Pin. Changing content, board or link means reviewing and rescheduling it; changed drafts can be blocked at publish until reviewed.\n- After an unclear publish failure, check Pinterest before retrying; the Pin may already exist.\n- After the first Pin publishes, check it and its link from outside the connected account. A draft, preview or accepted schedule is not delivery.\n\n## Measure what happened\n\n- **Impressions:** times the Pin was on screen.\n- **Saves:** times people saved it to a board.\n- **Pin clicks:** opens of the Pin in close-up.\n- **Outbound clicks:** clicks through to a destination outside Pinterest.\n- **Video views:** at least 2 seconds with at least 50% of the video in view.\n\nEach is dated daily activity, kept as its own number. Pinterest reports no likes, comments or shares here, and Sprid does not collect Pin comments, so there is nothing for a Pin in the Inbox.\n\nFor app growth the path is **impression → outbound click → qualified visit → app action → activation**. Saves are a diagnostic, not the goal: a Pin with many saves and few outbound clicks may be a good reference on Pinterest and bring little acquisition.\n\nCompare Pins of similar age within the same topic, language and market. Judge creative by outbound-click rate and qualified visits, the whole path by activations per Pin and revenue where available. Pinterest’s outbound clicks and your own site sessions are measured differently; show both rather than forcing them to match.\n\nReview cohorts at 30, 60 and 90 days of Pin age. Report counts and coverage, including results with the top Pin removed, so one outlier can’t set the strategy. Keep a higher frequency while extra Pins keep adding qualified visits or activations; a lower click rate alone doesn’t cancel growth in total qualified traffic.\n\n- Visits but no activation: check page speed, message match and the next app action before making more Pins.\n- Low impressions: check topic relevance, board, metadata and visual clarity.\n- Negligible qualified traffic and activation across repeated relevant cohorts: stop scaling Pinterest for that app.\n\n## Improve the next batch\n\nChange one meaningful thing per comparison (visual treatment, question, format or destination angle) and repeat across comparable destinations. Organic distribution isn’t randomized, so call it a directional test, not an A/B test.\n\nMake fresh, useful treatments, not cosmetic duplicates. [Tailwind’s freshness study](https://www.tailwindapp.com/pinterest-marketing/research/2025-benchmark-study-part-three) found new images for an existing URL kept distribution better than reusing the same image and URL (an association, not a guaranteed lift). Pinterest advises against re-uploading the same Pin, and repetitive or irrelevant commercial content can be treated as spam. Space treatments of the same destination apart and keep every Pin on a relevant board.\n\nFor a worked example, [Elaine Timms’s recipe-client case](https://elainetimms.com/evergreen-growth-how-food-creator-pins-for-profit/) combines search-specific overlays, creative variants and email opt-ins. Its growth can’t be credited to cadence alone.\n\n## Sources\n\n- [How Pinterest discovery works](https://create.pinterest.com/blog/how-to-increase-discoverability-seo-pinterest/)\n- [Growing through search, boards and keywords](https://create.pinterest.com/blog/best-ways-to-grow-on-pinterest/)\n- [Pin specifications](https://help.pinterest.com/en/article/review-pin-specs)\n- [Pin performance and distribution](https://help.pinterest.com/en/business/article/pin-performance-and-distribution)\n- [Pinterest Analytics definitions](https://help.pinterest.com/en/business/article/pinterest-analytics)\n- [Pinterest developer guidelines](https://policy.pinterest.com/en/developer-guidelines)\n- [Pinterest API schema](https://github.com/pinterest/api-description/blob/main/v5/openapi.json)\n- [Pinterest community guidelines](https://policy.pinterest.com/en/community-guidelines)\n- [Pinterest labels for AI-generated or modified images](https://help.pinterest.com/en/article/gen-ai-labels)\n- [Claim your website](https://help.pinterest.com/en/business/article/claim-your-website)\n- [Rich Pins](https://help.pinterest.com/en-gb/business/article/rich-pins)\n",
|
|
1701
|
+
"revision": "3f91ada0ff9d11ad"
|
|
1702
1702
|
},
|
|
1703
1703
|
{
|
|
1704
1704
|
"id": "distribution",
|
package/src/post/cli.mjs
CHANGED
|
@@ -1296,7 +1296,7 @@ export function crosspostCheck(m, caption, slug, platforms, lane = {}) {
|
|
|
1296
1296
|
pinterestOptions: m.post?.pinterestOptions,
|
|
1297
1297
|
platforms: modeled })
|
|
1298
1298
|
.flatMap((p) => p.problems.map((x) => `${p.platform}: ${x}`));
|
|
1299
|
-
const skips = unmodeled.length ? [`local projection rules unavailable for ${unmodeled.join(", ")}; use --deep for
|
|
1299
|
+
const skips = unmodeled.length ? [`local projection rules unavailable for ${unmodeled.join(", ")}; use --deep for scheduled posts`] : [];
|
|
1300
1300
|
// What the options slice cannot say: whether the MEDIA can be one Pin.
|
|
1301
1301
|
if (modeled?.includes("pinterest")) {
|
|
1302
1302
|
const media = pinterestMediaCheck(m, lane);
|
|
@@ -1540,7 +1540,7 @@ async function cmdCheck(cfg, argv) {
|
|
|
1540
1540
|
let deepProblems = [];
|
|
1541
1541
|
if (has(argv, "deep")) {
|
|
1542
1542
|
const rows = await deepCrosspost(cfg, targets);
|
|
1543
|
-
if (!rows.length) console.log(` ${C.ok("crosspost")} every
|
|
1543
|
+
if (!rows.length) console.log(` ${C.ok("crosspost")} every scheduled platform publishes what it should\n`);
|
|
1544
1544
|
else {
|
|
1545
1545
|
deepProblems = rows;
|
|
1546
1546
|
console.log(` ${C.bad(`crosspost: ${rows.length} problem(s) in Sprid`)}\n`);
|
|
@@ -2137,12 +2137,12 @@ async function cmdPull(cfg, argv) {
|
|
|
2137
2137
|
console.log(
|
|
2138
2138
|
` ${C.ok("pulled")} ${m.slug.padEnd(42)}` +
|
|
2139
2139
|
publishes.map((p) => `${p.platform[0]}${p.status === "published" ? C.ok("✓") : p.status === "scheduled" ? C.dim("·") : C.bad("!")}`).join(" ").padEnd(22) +
|
|
2140
|
-
C.dim(when ? when.slice(0, 16).replace("T", " ") : "not
|
|
2140
|
+
C.dim(when ? when.slice(0, 16).replace("T", " ") : "not scheduled") +
|
|
2141
2141
|
(failed.length ? ` ${C.bad(failed.map((f) => `${f.platform}: ${f.lastError}`).join("; ").slice(0, 60))}` : ""),
|
|
2142
2142
|
);
|
|
2143
2143
|
}
|
|
2144
2144
|
if (orphans)
|
|
2145
|
-
console.log(`\n ${C.warn(`${orphans} post(s) carry no publish row at all - drafted but never
|
|
2145
|
+
console.log(`\n ${C.warn(`${orphans} post(s) carry no publish row at all - drafted but never scheduled`)}`);
|
|
2146
2146
|
console.log();
|
|
2147
2147
|
}
|
|
2148
2148
|
|