Skip to main content
The seenpaid MCP server exposes 50 tools, every one scoped to the single organization behind your API key. An agent can never see or touch another account’s data — the server is built fresh per request and bound to that org. The tools fall into six groups:

Money & attribution

Read what earned — clicks, sales, revenue, and the honest match rate behind it.

Insight & timing

The patterns behind earning posts, a per-account forecast, and when to post next.

Publishing & posts

Draft, validate, schedule, edit, repost, and mint tracked links.

Media

Generate images and carousel slides, pull media from a URL, upload your own files, reuse the library.

Automation

Autopilot, revenue-decided A/B experiments, and auto-recycle.

Workspace & channels

Accounts, health, plan limits, notifications, workspaces, connect links.
Money is returned twice. Every revenue figure comes back both as an integer revenueCents (for arithmetic) and as a pre-formatted revenue string with the currency symbol — so an agent quoting a number back to a human can’t fumble the decimal point. When you display a figure, use the string; when you compare or sum, use the cents.

Money and attribution

Every other tool here answers what did I post; these answer what did it earn.
The nine tools in this group, plus get_channel_roi and create_tracked_link, need revenue attribution switched on for the workspace. With it off (the default) they return one sentence, “Revenue attribution is off for this workspace — turn it on in Settings (Settings → Attribution) to track clicks and sales per post. Scheduling tools are unaffected.”, rather than zeros that would read as “no sales”.

get_analytics

The account’s revenue attribution over a window — clicks, sales, and revenue — with per-platform, per-country and per-device breakdowns, plus the change versus the previous equal-length period.
7 | 30 | 90
Window in days. Defaults to 30.
string
Restrict every figure to one platform (e.g. linkedin).
Returns window_days, currency, totals (clicks, payments, revenueCents, revenue), vs_previous_period (clicks_pct, sales_pct, revenue_pct), by_platform, top_countries, by_device, and the funnel.
“How’s my revenue this week, and which platform drove the most?”

get_analytics_breakdown

Slice attribution by a single dimension — where the traffic and money actually came from. Rows include revenue per click, the number that actually ranks sources: a referrer with 10 clicks and one $200 sale beats one with 900 clicks and nothing.
string
required
One of country, device, referrer, os, browser, page, utm_source, utm_medium, utm_campaign.
7 | 30 | 90
Window in days. Defaults to 30.
number
Max rows, 1–50. Defaults to 10.
Returns dimension, window_days, currency, and rows (each with clicks, payments, revenueCents, revenue, revenuePerClickCents).
“Which referrer converts best for me?”

get_analytics_timeseries

Day-by-day clicks, sales and revenue across a window, with the change from the previous day already computed for you (asking a model to subtract 90 pairs of numbers is a reliable way to get one wrong). Use for trend questions rather than a single total.
7 | 30 | 90
Window in days. Defaults to 30.
Returns window_days, currency, bestDay, and a series of { date, clicks, sales, revenueCents, revenue, clicksChange, revenueChangeCents }.
“Is my revenue trending up, and which day spiked?”

get_top_posts

Posts ranked by revenue earned. Answers “which post made the most money” and “what should I do more of”.
number
Max posts, 1–50. Defaults to 10.
Returns count and posts (each postId, caption, publishedAt, clicks, sales, revenueCents, revenue).
“Which of my posts made the most money this month?”

get_dead_posts

Posts that got clicks but earned nothing — the content that looks like it worked and didn’t. The single most actionable list in seenpaid: it tells you what to stop making.
number
Only count posts with at least this many clicks, 1–10000. Defaults to 1.
number
Max posts, 1–50. Defaults to 10.
Returns count, a note, and posts (each postId, caption, publishedAt, clicks).
“Which posts got clicks but earned nothing?”

get_post_revenue

Revenue, clicks and sales for one specific post.
string (uuid)
required
The post id (from list_posts or get_top_posts).
Returns the post’s clicks, payments, revenueCents, revenue, currency.
“How much has post <id> earned?”

get_channel_roi

Rank connected platforms by what they actually return — revenue, revenue per click, and revenue per post. Answers “where should I spend my effort” with money instead of follower counts.
7 | 30 | 90
Window in days. Defaults to 30.
Returns window_days, currency, a note, and platforms sorted by revenuePerPostCents (each with posts, clicks, sales, revenueCents, revenue, revenuePerPostCents, revenuePerClickCents).
“Which channel actually returns the most per post?”

get_money_feed

The live activity stream — recent clicks, sales and refunds, newest first, each with the platform and country it came from.
number
Max events, 1–50. Defaults to 25.
Returns count and events (each with its type, platform, country, and — for sales/refunds — an amount string).
“What just happened on my account — any sales in the last hour?”

get_attribution_health

How much of this account’s real Stripe revenue seenpaid can trace back to a post. A low match rate means the tracking setup is incomplete, not that the posts failed — check this before trusting a low revenue number. Input: none. Returns the matched share (matchedPct) and the totals behind it.
“What share of my Stripe sales is being traced to a post?”

summarize_performance

A single briefing an agent can read aloud: what went out, what earned, what died, what’s broken, and what to do next. Use this for “how are we doing” rather than calling five tools and stitching them together.
7 | 30 | 90
Window in days. Defaults to 7.
Returns headline, totals, attribution_match_pct, top_earners, dead_posts, broken_channels, and a recommended_actions list.
“Give me a briefing on the last 7 days.”

Insight and timing

Derived from posts that actually earned. These stay quiet until an account has a few earning posts — they report not enough data yet rather than guessing.

get_money_dna

The patterns behind this account’s earning posts — best platform, best caption length, whether links help, best posting time. Input: none. Returns the derived insights, or { enough: false, earningPosts, note } when there isn’t enough earning history yet.
“What do my best-earning posts have in common?”

forecast_post

Predict what a draft would earn, based on this account’s own history — returns a low/high range and the factors driving it. Use it before scheduling to compare two versions of a caption.
string
required
The draft text (up to 3000 characters).
string[]
Where it would go.
boolean
Whether the post would include an image or video.
Returns score, predicted_range (a formatted string), predictedLowCents, predictedHighCents, currency, and factors — or { enough: false, note }.
“Forecast this caption, then schedule it if it beats my average.”

get_next_slot

Suggest when to schedule the next post. Picks the soonest time on the weekday that has actually earned this account the most money, skipping slots already taken. Falls back to “a few hours from now” when there isn’t enough revenue history to have an opinion.
number
Preferred hour of day in UTC, 0–23. Defaults to 15.
Returns suggested (ISO time), a human reason, basedOnRevenue, and slotsAlreadyTaken.
“When should I post next for the best shot at a sale?”

Publishing and posts

Publishing tools attribute the post to the user who created the API key. If that user was removed and the org has no owner, they return a clear error telling you to create a new key from Settings → AI agents & API.

list_posts

List posts with status, schedule time, target platforms and any publish errors.
string
Filter to one of draft, scheduled, publishing, published, failed, cancelled.
number
Max posts, 1–100. Defaults to 20.
Returns count, totalPosts, and posts (each id, caption, status, scheduledFor, publishedAt, platforms, mediaCount, and errors when present).
“What do I have scheduled this week?”

get_post

Get one post in full, including per-platform publish results — which platforms succeeded, the live URL of each published copy, and the exact error for any that failed.
string (uuid)
required
Post id from list_posts.
Returns the post’s caption, status, scheduledFor, publishedAt, and targets (the per-platform results).
“Did post <id> publish everywhere, or did one platform fail?”

schedule_post

Schedule or immediately publish a post. Give a caption plus either platforms or account_ids; omit both to post to every active account. Omit schedule_for to publish now.
string
required
The post text (1–3000 characters).
string[]
Platforms to post to, e.g. ["x", "linkedin"].
string[]
Specific account ids from list_accounts. Overrides platforms.
string (ISO-8601)
A future time to publish. Omit to publish immediately.
string[]
Up to 10 media ids from generate_image, add_media_from_url, or the dashboard uploader.
object
Override the caption for specific account ids, e.g. { "<account_id>": "shorter text" } — handy for tailoring the copy to X’s 280-character limit.
Returns { ok, postId, status, scheduledFor, postedTo }.
“Post ‘New drop is live: mystore.com’ to X and LinkedIn tomorrow at 9am.”

bulk_schedule

Schedule many posts in one call — a content calendar, a thread split across days, a week of promos. Each item is scheduled independently; if one fails the rest still go through, and failures are reported per item.
array
required
1–50 items, each { caption, schedule_for?, platforms?, media_ids? }.
Returns scheduled, failed, and a per-item results array.
“Plan me a week of posts and schedule them all.”

update_post

Edit a draft or scheduled post — change its caption, its time, or both. Published posts can’t be edited (the platforms already have them). Give at least one of caption or schedule_for.
string (uuid)
required
string
New caption (1–3000 characters).
string (ISO-8601)
New future time. Re-queues the publish jobs.
Returns { ok, id, caption, status, scheduledFor }.
“Move my Friday post to Saturday 10am and shorten the caption.”

cancel_post

Cancel a scheduled post before it goes out. Stops every pending platform job; platforms it already published to are untouched. The post row survives — use delete_post to remove it entirely.
string (uuid)
required
Returns { ok, id, status }.
“Cancel the post scheduled for tonight.”

delete_post

Permanently delete a post and cancel any pending publishes. This cannot be undone — prefer cancel_post unless the user explicitly asked to delete.
string (uuid)
required
true
required
Must be true. Deliberate friction so a post is never deleted by an ambiguous instruction.
Returns { ok, deleted }.

repost

Re-publish a post that already worked, as a brand-new post with fresh tracking. The classic use: call get_top_posts, then repost the top earner. The original is left untouched.
string (uuid)
required
The post to re-run.
string (ISO-8601)
A future time. Omit to publish now.
string
Replace the caption; omit to reuse the original word for word.
string[]
Send it somewhere different; omit to reuse the original platforms.
Returns { ok, newPostId, recycledFrom, status, scheduledFor, platforms, reusedMedia }.
“Repost my top earner for next Tuesday.”
Mint a tracked short link for a post. Clicks on it are attributed to that post, and any Stripe sale that follows is traced back to it. Use this when publishing somewhere seenpaid doesn’t post to directly — a newsletter, a video description, a manual post — so the revenue still lands on the right post.
string (uuid)
required
The post this link belongs to.
string (url)
required
Where the link should send people.
string
required
Which platform the link will be shared on — this is how revenue is attributed per channel.
string
Optional label shown on the bio page.
Returns { ok, trackedUrl, trackingId, note }.
“Give me a tracked link for post <id> to put in my YouTube description.”

validate_post

Dry-run a caption against the platforms you plan to send it to, without publishing. Reports per platform whether it would publish and exactly why not — too long by N characters, media required and none attached, account disconnected. Call this before schedule_post whenever one caption goes to several platforms.
string
required
The caption you intend to publish.
string[]
required
Where you intend to send it.
boolean
Whether the post will include an image or video.
Returns allClear, a summary, and per-platform results (wouldPublish, problems, charactersUsed, characterLimit, and any caveat).
“Will this caption post cleanly to X, LinkedIn and Instagram?”

get_platform_requirements

What each platform will and won’t accept: caption character limit, whether media is mandatory, whether links in the post body can be tracked, and any platform-specific catch. Check this before writing captions for several platforms at once.
string[]
Limit to these platforms; omit to report on every supported channel.
boolean
Only report on platforms this account has connected.
Returns a platforms array of { platform, captionLimit, requiresMedia, supportsTrackedLinksInPost, caveat? }.
“What are the caption limits for the platforms I’ve connected?”

Media

generate_image

Generate an image from a text prompt and store it in the account’s media library. Returns a media id you can pass straight to schedule_post — the fastest way from an idea to a post with visuals.
string
required
What the image should show, 3–1000 characters.
Returns { ok, mediaId, url, type }.
“Make an image of a laptop and coffee in warm morning light, then attach it.”

add_media_from_url

Pull an image or video from a public URL into the account’s media library and get back a media id.
string (url)
required
Public URL of the image or video (jpeg, png, webp, mp4 or mov; 100MB max).
Returns { ok, mediaId, url, type, sizeBytes }.
“Add the image at <url> to my library and use it in tomorrow’s post.”

list_media

The account’s media library, newest first. Returns ids you can pass straight to schedule_post instead of regenerating something that already exists.
number
Max items, 1–100. Defaults to 25.
Returns count and media (each id, url, type, width, height, createdAt).
“Show me what’s already in my media library.”
Render 2–10 branded carousel slides (1080×1080 PNGs: a title slide plus numbered content slides, dark brand background, your handle in the footer) from text you write, and get back ordered media ids for schedule_post. It only renders; it doesn’t write copy. Platforms cap attached images (LinkedIn 9; X, Bluesky and Mastodon 4), and Instagram isn’t supported for these slides.
array
required
2–10 slides in order; the first becomes the title slide. Each has a heading (required, ≤120 chars), optional kicker (≤60), body (≤400) and image_prompt (≤300) for an AI-generated background drawn under a scrim.
string
required
Shown in the footer of every slide, e.g. @yourbrand.
string
Hex accent override, e.g. #FFBF65 (the default).
Returns the ordered media ids and URLs.
“Turn these five tips into a LinkedIn carousel and schedule it for Monday.”

create_media_upload

Step 1 of 2 for uploading a file you already have (for a file at a public URL use add_media_from_url). Returns a URL to HTTP PUT the bytes to; the bytes go straight to storage, so large videos are fine.
string
required
Original filename, e.g. clip.mp4; only the extension is kept.
string
required
One of image/jpeg, image/png, image/webp, video/mp4, video/quicktime.
Returns { ok, uploadUrl, r2Key }. PUT the file to uploadUrl, then call finish_media_upload.

finish_media_upload

Step 2 of 2: confirms the bytes arrived (checked against storage, not against what you report) and returns a media id for schedule_post. Call it only after the PUT succeeded.
string
required
The r2Key returned by create_media_upload.
string
required
The same content type you uploaded with.
Returns { ok, mediaId, url, type }.

Automation

The three standing automations. These are what make an external agent genuinely useful rather than just a remote control: it can set a policy and check on it, instead of being present for every post.

Autopilot

Autopilot drafts posts for you on a schedule. Nothing publishes without approval — approving a suggestion returns its caption for you to schedule with schedule_post.

get_autopilot_status

Whether autopilot is on, how often it generates drafts, which platforms it targets, and how many suggestions are waiting for approval. Input: none.

configure_autopilot

Turn autopilot on/off and set its cadence, batch size, target platforms and topic.
boolean
required
number
required
Generate a new batch every N days, 1–30.
number
required
How many drafts per batch, 1–10.
string[]
Which platforms to write for (up to 6).
string
What to write about, e.g. “indie SaaS growth lessons”.
“Turn on autopilot: 3 drafts every 4 days about my product, for X and LinkedIn.”

generate_suggestions

Run autopilot immediately instead of waiting for the next scheduled batch. Input: none. Returns how many drafts were created, or a clear error if autopilot is off or unconfigured.

list_suggestions

The autopilot-generated drafts waiting for a decision, each with an id for approve_suggestion or dismiss_suggestion. Input: none.

approve_suggestion

Approve an autopilot draft. Returns its caption and intended platform so you can then schedule it — approving does not publish on its own.
string (uuid)
required

dismiss_suggestion

Reject an autopilot draft so it stops appearing in the queue.
string (uuid)
required
“Review my autopilot drafts, approve the best one, and schedule it for Tuesday 9am.”

Experiments (A/B by revenue, not by likes)

The winner is decided on revenue earned, not engagement. Each variant goes out as a real post with its own tracked link.

list_experiments

All caption A/B tests with their variants, status, and which variant is winning on revenue. Input: none.

get_experiment

One A/B test in full — every variant with its clicks, sales and revenue.
string (uuid)
required

create_experiment

Create a caption A/B test: give one idea and a platform, and seenpaid writes N variants. Nothing goes live until launch_experiment.
string
required
What the post should be about, 3–300 characters.
string
required
Which platform to test on.
number
How many variants, 2–4. Defaults to 3.

launch_experiment

Publish every variant of an A/B test. Each goes out as a real post with its own tracked link, so revenue can be attributed per variant.
string (uuid)
required

get_experiment_winner

The winning caption of a finished A/B test, ready to reuse or scale up.
string (uuid)
required
“A/B test three hooks for my launch post on X, launch them, and tell me the winner.”

Recycle

get_recycle_status

The rule for automatically re-posting content that already earned money, and which posts currently qualify. Input: none.

configure_recycle

Set the auto-recycle rule: re-post anything that earned at least min_revenue_cents, no more often than every cadence_days. Turns proven winners into a repeating stream instead of one-offs.
boolean
required
number
required
Minimum days between re-posts of the same content, 1–90.
number
required
Only recycle posts that earned at least this much, in cents (0–10,000,000).
“Auto-recycle any post that earned over $100, at most once every 14 days.”

Workspace and channels

list_accounts

The connected social accounts — platform, handle, id and status. Use these ids or platforms when scheduling. Input: none. Returns count and accounts (each id, platform, name, status).
“Which social accounts do I have connected?”

get_account_health

Which connected accounts are healthy and which stopped working. An account that needs reconnecting will silently fail to publish, so check this when a post didn’t go out or before scheduling a big batch. Input: none. Returns healthy, broken, and needsAttention (each with a fix describing exactly what to do).
“Is anything broken — did a channel stop publishing?”

get_plan_limits

This account’s plan and its caps — how many social accounts and Stripe accounts it can connect, and how many are already used. Input: none. Returns plan, socialAccounts (used, limit, remaining), and stripeAccountLimit.
“How many more channels can I connect on my plan?”

list_notifications

The account’s alerts — sales that landed, publish failures, milestones. Use to catch problems the user hasn’t seen yet.
number
Max notifications, 1–50. Defaults to 20.
Returns unreadCount and notifications.
“Any new alerts I should know about?”

list_workspaces

The workspaces (brands or clients) this account belongs to. Each has its own posts, channels and revenue — an API key is scoped to exactly one, so this is how an agent knows which set of numbers it’s looking at. Input: none. Returns count, currentOrgId, and workspaces (each flagged isCurrent).
“Which workspace is this key pointed at?”

get_connect_url

Get the URL a human needs to open to connect a new social account. The agent can’t complete OAuth itself — hand this link to the user.
string
required
Which platform to connect.
string (url)
required
Where the platform should send the user back to, e.g. https://seenpaid.com/accounts/callback.
Returns { ok, connectUrl, note }.
“Give me a link to connect my LinkedIn.”

disconnect_account

Disconnect a social account. Scheduled posts targeting only that account will stop publishing, so check list_posts first.
string (uuid)
required
true
required
Must be true — deliberate friction so an account is never dropped by an ambiguous instruction.
Returns { ok, disconnected }.
New to the money tools? The natural first call for any account is summarize_performance — it reads out what earned, what died, what’s broken and what to do next in a single response.

Connect the server

Endpoint, client config, and a first request.

Prompt library

Ready-to-use prompts, grouped by what they do.