Authentication
Every request to https://mcp.postnext.io/api requires an Authorization header with a Bearer token. You have two ways to get one.
OAuth (recommended for AI clients)
AI clients run Dynamic Client Registration on first connect, then walk the user through an OAuth consent screen. The /mcp/connect page handles this automatically. Your client receives a 30-day access token.
API key
For scripts and CI, generate a personal API key in the PostNext app under Account → API Keys. The key starts with apikey_ and is presented as a bearer token like any other.
Endpoint
All tool calls go to a single JSON-RPC 2.0 endpoint over HTTPS POST.
Tools
list_scheduled_posts
List your scheduled posts (queued for publish but not yet sent).
Arguments
limitfromReturns
An object with posts: [{id, platform, content, scheduledAt, status}]. Up to limit results.
Example
curl -X POST https://mcp.postnext.io/api \
-H "Authorization: Bearer apikey_xxx" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","id":1,
"params":{"name":"list_scheduled_posts",
"arguments":{"limit":5}}}'
list_drafts
List your draft posts (not yet scheduled).
Arguments
limitReturns
An object with drafts: [{id, platform, content, updatedAt}]. Up to limit results.
Example
{"jsonrpc":"2.0","method":"tools/call","id":2,
"params":{"name":"list_drafts","arguments":{"limit":10}}}
list_connected_accounts
List the social accounts connected to your team.
Arguments
No arguments.
Returns
An object with accounts: [{platform, handle, connected, lastSyncAt}].
Example
{"jsonrpc":"2.0","method":"tools/call","id":3,
"params":{"name":"list_connected_accounts","arguments":{}}}
get_account_healthPaid plan
Get a health summary across all connected platforms.
Arguments
platformwindowDaysReturns
An object with windowDays, postsScheduled, postsPublished, postsFailed, a byPlatform breakdown and topErrors[].
Example
{"jsonrpc":"2.0","method":"tools/call","id":4,
"params":{"name":"get_account_health",
"arguments":{"windowDays":30}}}
create_post_draft
Create a new draft post. Drafts are not auto-published.
Arguments
platformcontentmediaUrlsclientRequestIdReturns
An object with the new draft: {id, platform, content, status: "DRAFT", createdAt}.
Example
{"jsonrpc":"2.0","method":"tools/call","id":5,
"params":{"name":"create_post_draft",
"arguments":{"platform":"twitter",
"content":"Hello from MCP"}}}
update_post_draft
Update an existing draft. At least one of content or mediaUrls must be provided.
Arguments
postIdcontentmediaUrlsclientRequestIdReturns
An object with the updated draft: {id, platform, content, mediaUrls, status, updatedAt}.
Example
{"jsonrpc":"2.0","method":"tools/call","id":6,
"params":{"name":"update_post_draft",
"arguments":{"postId":"",
"content":"Updated copy"}}}
connect_channel
Generate a one-time URL the user can open to authorize a new platform connection.
Arguments
platformReturns
An object with authUrl (one-time link the user opens to authorise), expiresAt and platform.
Example
{"jsonrpc":"2.0","method":"tools/call","id":7,
"params":{"name":"connect_channel",
"arguments":{"platform":"linkedin"}}}
schedule_post
Move a draft (status DRAFT) into the BullMQ publish queue at a future timestamp. Idempotent within ±60s drift; rejects re-schedule attempts at a different time without explicit cancel first.
Arguments
postIdscheduledAtReturns
An object with postId, scheduledAt (ISO), status: SCHEDULED.
Example
{"jsonrpc":"2.0","method":"tools/call","id":8,
"params":{"name":"schedule_post",
"arguments":{"postId":"",
"scheduledAt":"2026-05-10T09:00:00Z"}}}
cancel_scheduled_post
Cancel a scheduled post and remove it from the publish queue. The underlying draft is preserved.
Arguments
postIdReturns
An object with postId and status: CANCELLED.
Example
{"jsonrpc":"2.0","method":"tools/call","id":9,
"params":{"name":"cancel_scheduled_post",
"arguments":{"postId":""}}}
delete_draft
Permanently delete a draft post. Refuses non-DRAFT posts (use cancel_scheduled_post for SCHEDULED).
Arguments
postIdReturns
An object with postId and deleted: true.
Example
{"jsonrpc":"2.0","method":"tools/call","id":10,
"params":{"name":"delete_draft",
"arguments":{"postId":""}}}
get_post
Return the full PostGroup for a given postId with provider-by-platform content + status + timestamps.
Arguments
postIdReturns
An object with postId, status, scheduledAt, createdAt, updatedAt and providers (record of platform → content).
Example
{"jsonrpc":"2.0","method":"tools/call","id":11,
"params":{"name":"get_post",
"arguments":{"postId":""}}}
search_posts
Substring (case-insensitive) search across post content for the active team. Filters: status, platform, limit (1-100, default 20).
Arguments
querystatusplatformlimitReturns
An object with posts: [{postId, status, platforms, snippet, scheduledAt, updatedAt}]. Snippet contains the matching context with ellipsis on either side.
Example
{"jsonrpc":"2.0","method":"tools/call","id":12,
"params":{"name":"search_posts",
"arguments":{"query":"launch","limit":10}}}
list_teams
Return the teams the authenticated user owns or admins, with the currently active team flagged.
Arguments
No arguments.
Returns
An object with currentTeamId and teams: [{teamId, teamName, isCurrent}].
Example
{"jsonrpc":"2.0","method":"tools/call","id":13,
"params":{"name":"list_teams","arguments":{}}}
set_current_team
Switch the active team for this Bearer token. The user must be a member. Persisted on the OAuth token doc; in-session mutation makes it effective immediately for subsequent tools in the same conversation.
Arguments
teamIdReturns
An object with teamId, teamName, persisted (true if OAuth, false if API key) and an optional note.
Example
{"jsonrpc":"2.0","method":"tools/call","id":14,
"params":{"name":"set_current_team",
"arguments":{"teamId":"team_xxxxx"}}}
get_plan_limits
Read the user's current subscription tier and per-tier quotas (channels, storage, AI credits, posting). Available on every plan. Useful before mutating tools to predict whether the call will be blocked.
Arguments
No arguments.
Returns
An object with tier, tierDisplayName, isPaid, isTrial, upgradeUrl, limits (channels/storage/aiCalls/posts each as {limit, current, exceeded} or {limit, allowed}) and creditsAvailable ({total, subscriptionRemaining, purchasedBalance} or null).
Example
{"jsonrpc":"2.0","method":"tools/call","id":15,
"params":{"name":"get_plan_limits","arguments":{}}}
get_post_metrics
Fetch engagement metrics for a published post across every platform it was sent to. Twitter and Instagram return populated metrics (likes, retweets/shares, comments/replies, impressions); LinkedIn, Threads and TikTok return publish status and URL only (engagement collection deferred to a later release). A normalised totals roll-up sums likes/comments/shares/impressions across whichever platforms reported them.
Arguments
postId PostNext post id from a list call.
Returns
An object with postId, status, providers: [{platform, publishedPostId, publishedUrl, publishedAt, status, metrics}] and totals: {likes, comments, shares, impressions}. metrics is an object of numeric counts per platform, or null when the platform does not yet collect engagement.
Example
{"jsonrpc":"2.0","method":"tools/call","id":16,
"params":{"name":"get_post_metrics",
"arguments":{"postId":""}}}
get_publish_status
Diagnose what happened to a scheduled post: queue state, per-platform success/error, processing time, and the worker's job id. Returns isScheduled=false for posts that exist as drafts but were never moved into the publish queue, with a hint pointing at schedule_post.
Arguments
postId PostNext post id from a list call.
Returns
An object with postId, isScheduled, status, scheduledAt, publishedAt, platforms, processingTimeMs, jobId, topLevelError, and results: [{platform, success, publishedPostId, publishedAt, error}]. Includes hint when isScheduled is false.
Example
{"jsonrpc":"2.0","method":"tools/call","id":17,
"params":{"name":"get_publish_status",
"arguments":{"postId":""}}}
update_brand_profile
Patch the user's active brand profile. Whitelisted fields: bio, brandVoice, personalityTraits, mainThemes, expertiseAreas, preferredHashtags, audienceSize. Anything else is silently dropped. Resolves "active" as team default first, then the user's first own active profile.
Arguments
brandVoice Array of brand-voice descriptors (e.g. ["professional", "concise"]).
Other patchable fields: bio, personalityTraits, mainThemes, expertiseAreas, preferredHashtags, audienceSize.
Returns
An object with profileId, name, updated: [field names that were patched], and the current values of bio, brandVoice, personalityTraits, mainThemes, expertiseAreas, preferredHashtags, audienceSize after the patch.
Example
{"jsonrpc":"2.0","method":"tools/call","id":18,
"params":{"name":"update_brand_profile",
"arguments":{"brandVoice":["professional","concise"]}}}
get_best_time_to_post Paid plan
Recommend the top N (dayOfWeek, hour) slots for the next post on a given platform, ranked by engagement-per-post over the past 90 days. Twitter and Instagram return engagement-weighted scores; LinkedIn, Threads, TikTok rank by post-frequency only. Times are in the supplied IANA timezone (default UTC). Paid plan required.
Arguments
platform Social platform: twitter, instagram, linkedin, threads, or tiktok.
timezone IANA timezone name (e.g. Europe/Berlin, America/New_York). Defaults to UTC.
topN Number of slot recommendations to return (1-24, default 5).
Returns
An object with platform, timezone, sampleWindowDays, sampleSize, metricsAvailable, and recommendations: [{dayOfWeek (1-7, Mon=1), dayLabel, hour (0-23), score, basedOn: {posts, avgEngagement}}]. Includes hint for cold-start or metrics-unavailable cases.
Example
{"jsonrpc":"2.0","method":"tools/call","id":19,
"params":{"name":"get_best_time_to_post",
"arguments":{"platform":"twitter","timezone":"Europe/Berlin","topN":5}}}
get_channel_analytics
Aggregate performance for one connected channel, or for every channel at once. Returns impressions, reach, likes, comments and engagement rate for the period, the change against the preceding period of the same length, and the follower series. Omit platform for a team-wide total. Use get_post_metrics for a single post and get_best_time_to_post for scheduling.
Arguments
platform Social platform: twitter, instagram, linkedin, threads, or tiktok.
channelName Which connected account, when the platform has more than one.
period Window to report on: 7d, 30d, 90d or all (default 30d).
Returns
An object with scope, period, totals, vsPrevious, coverage, dataThrough and followers, which carries trackingSince, current, change and the series.
Example
{"jsonrpc":"2.0","method":"tools/call","id":34,
"params":{"name":"get_channel_analytics",
"arguments":{"platform":"linkedin","period":"30d"}}}search_tools
Search the MCP tool catalog by free-text query, category, or intent. Returns categories, requiresScope, one-line descriptions, and the use-case tags that matched. Free for all plans (discovery surface).
Arguments
All optional. Call with no arguments to list every available tool.
query Free-text search across tool names, descriptions, and use-case tags.
category Filter to one category: posts, accounts, analytics, brand, teams, discovery, other.
Returns
An object with totalTools, matchCount, categories: [string[]], and tools: [{name, category, requiresScope, description, useCases, matchedOn: [name|category|description|use_case]}]. Includes hint when no match or no filter.
Example
{"jsonrpc":"2.0","method":"tools/call","id":20,
"params":{"name":"search_tools",
"arguments":{"query":"why did my post fail"}}}
get_plan
Plan identity and billing dates: tier, renewal date, trial end date, and whether the subscription is set to cancel at the period end. For quotas and usage, call get_plan_limits instead.
Arguments
No arguments.
Returns
An object with tier, tierDisplayName, isPaid, isTrial, gated, subscriptionStatus, renewsAt, trialEndsAt, cancelAtPeriodEnd, currency and upgradeUrl.
Example
{"jsonrpc":"2.0","method":"tools/call","id":21,
"params":{"name":"get_plan",
"arguments":{}}}
upload_asset
Inline base64 upload of an image or video to the asset library. Reliable only for very small files, around 8KB or less: a larger payload silently truncates in the tool arguments and stores a corrupt file. Use request_asset_upload for anything bigger.
Arguments
filename File name including its extension, up to 255 characters.
contentType MIME type of the file, from the allowed image and video list.
base64 The file bytes, base64 encoded. Keep this tiny, and read the warning above first.
Returns
An object with assetId, url, sizeBytes and mimeType. Pass the url to create_post_draft as mediaUrls.
Example
{"jsonrpc":"2.0","method":"tools/call","id":22,
"params":{"name":"upload_asset",
"arguments":{"filename":"logo.png","contentType":"image/png","base64":"iVBORw0KGgo..."}}}
request_asset_upload
The recommended upload path, for images and video of any size. Two steps: call this tool to get a pre-signed URL and a ready-made curl command, then PUT the file bytes to that URL. The PUT response carries the final asset URL.
Arguments
filename File name including its extension, up to 255 characters.
contentType MIME type of the file, from the allowed image and video list.
sizeBytes Exact size of the file in bytes, used to sign the upload.
Returns
An object with uploadId, uploadUrl, uploadToken, expiresInSeconds and instructions holding the exact PUT command.
Example
{"jsonrpc":"2.0","method":"tools/call","id":23,
"params":{"name":"request_asset_upload",
"arguments":{"filename":"clip.mp4","contentType":"video/mp4","sizeBytes":4194304}}}
get_latest_blog_post
Whether the team has a WordPress blog connected, plus a snippet of the most recent post. Read-only and free of credit cost. Call it before create_blog_plan or test_blog_connection to see what is already wired up.
Arguments
No arguments.
Returns
An object with blogConnected, an integration summary carrying no secrets, and latestPost with its title, snippet, status and published URL.
Example
{"jsonrpc":"2.0","method":"tools/call","id":24,
"params":{"name":"get_latest_blog_post",
"arguments":{}}}
list_blog_plans
List the blog plans belonging to the team, newest first, with status, per-status topic counts, credit reservations and a dashboard link. Read-only and free of credit cost.
Arguments
limit Maximum plans to return (1-50, default 20).
status Filter by status: generating, active, paused, completed, cancelled or failed.
Returns
An object with plans and total. Each plan carries its id, status, topic counts and dashboard link.
Example
{"jsonrpc":"2.0","method":"tools/call","id":25,
"params":{"name":"list_blog_plans",
"arguments":{"status":"active","limit":10}}}
create_blog_plan Paid plan
Create a blog content plan for a brand profile. Topics generate asynchronously, so the plan returns immediately in status generating: poll list_blog_plans for progress. On Business and Teams plans the posts are also generated and published to a connected WordPress blog.
Arguments
startDate First day the plan covers, as YYYY-MM-DD.
planDays How many days the plan covers (1-30, default 7).
brandProfileId Brand profile to plan for. Defaults to the active profile on the team.
integrationId Which connected blog to use, when the team has more than one.
autoPublish Publish the generated posts to the connected blog automatically (Business and Teams).
Returns
An object with planId, status, startDate, planDays, brandProfileId, integrationId, topicCount and dashboardUrl.
Example
{"jsonrpc":"2.0","method":"tools/call","id":26,
"params":{"name":"create_blog_plan",
"arguments":{"startDate":"2026-10-01","planDays":14,"autoPublish":true}}}
test_blog_connection Paid plan
Check that the connected WordPress blog answers and that its token is still valid, and record the result on the integration. Returns the plugin version and what it can do.
Arguments
integrationId Which connected blog to use, when the team has more than one.
Returns
An object with success, message, version, capabilities, integrationId and the updated status.
Example
{"jsonrpc":"2.0","method":"tools/call","id":27,
"params":{"name":"test_blog_connection",
"arguments":{}}}
list_mini_sites
List the bio pages belonging to the selected team, with their public URL, publish state and setup progress. Start here: every other bio-page tool takes the siteId this returns, and can omit it when the team has exactly one site.
Arguments
No arguments.
Returns
An object with sites, each carrying id, slug, name, published, url, views and setup progress.
Example
{"jsonrpc":"2.0","method":"tools/call","id":28,
"params":{"name":"list_mini_sites",
"arguments":{}}}
get_mini_site
Read one bio page in full: profile header, social icons, every content block, SEO metadata, theme and auto-pin settings. Long text is cut to a 300-character preview, and credentials are never returned.
Arguments
siteId Which bio page to act on. Omit it when the team has exactly one.
Returns
An object with id, slug, published, url, header, socials, meta, theme, autoPin, campaign and the blocks array.
Example
{"jsonrpc":"2.0","method":"tools/call","id":29,
"params":{"name":"get_mini_site",
"arguments":{}}}
get_mini_site_analytics
How a bio page performs: views, clicks, best links, visitor source, device and country, and AI-crawler reads, plus which published posts drove clicks. Every figure except the attribution rows is an all-time counter rather than a windowed one, so say so when reporting numbers.
Arguments
siteId Which bio page to act on. Omit it when the team has exactly one.
attributionDays Window in days for post-to-click attribution (1-90, default 30).
Returns
An object with viewsAllTime, clicksAllTime, topLinks, bySource, byDevice, byCountry, ai, attribution and attributionWindowDays.
Example
{"jsonrpc":"2.0","method":"tools/call","id":30,
"params":{"name":"get_mini_site_analytics",
"arguments":{"attributionDays":30}}}
add_mini_site_block
Add one block to a bio page: link, featured, product, event, pre-save, podcast episode, booking, opening hours, header, text, image or gallery. Required fields differ by type. This changes a publicly visible page straight away.
Arguments
siteId Which bio page to act on. Omit it when the team has exactly one.
type Type of block to add, for example link, product, event, hours or gallery.
... Plus the fields for that block type, for example url, title, price or startDate.
Returns
An object with the stored block, the new blockCount, and alreadyExisted when the same block was already present.
Example
{"jsonrpc":"2.0","method":"tools/call","id":31,
"params":{"name":"add_mini_site_block",
"arguments":{"type":"link","url":"https://example.com","title":"New single"}}}
update_mini_site_block
Patch one existing block: a link title or URL, a product price, an event date, opening hours, or the display order. Only the fields you pass change, and the block type cannot be changed. The response returns the block as stored, so any value the server rejected is visible.
Arguments
siteId Which bio page to act on. Omit it when the team has exactly one.
blockId Id of the block, as returned by get_mini_site.
... Plus the fields for that block type, for example url, title, price or startDate.
Returns
An object with the block as stored, the list of fields updated, and any fields ignored.
Example
{"jsonrpc":"2.0","method":"tools/call","id":32,
"params":{"name":"update_mini_site_block",
"arguments":{"blockId":"blk_7f3a","title":"Updated title"}}}
remove_mini_site_block
Remove one block from a bio page by its blockId, for example a finished tour date or a retired product link. Call get_mini_site first to find the id. This changes a publicly visible page straight away.
Arguments
siteId Which bio page to act on. Omit it when the team has exactly one.
blockId Id of the block, as returned by get_mini_site.
Returns
An object with removed, the blockId, and how many blocks remain.
Example
{"jsonrpc":"2.0","method":"tools/call","id":33,
"params":{"name":"remove_mini_site_block",
"arguments":{"blockId":"blk_7f3a"}}}
MCP Resources
Resources are read-only context the client can fetch via the MCP resources/read method. Claude pulls them at conversation start so it has account/team/brand context without you re-asking each session.
postnext://account/summary
Account email and handle (so Claude can confirm which account it's operating on), plan, AI credits used / limit, draft count, scheduled count, connected channel count. Counts capped at 100 each for fast load.
postnext://channels/connected
Same shape as list_connected_accounts result; exposed as a resource so Claude pulls it once at conversation start (no tool-call round-trip).
postnext://brand-profiles/active
The user's active brand profile (team default, falling back to first own profile). Returns only LLM-relevant fields: bio, expertiseAreas, personalityTraits, brandVoice, mainThemes, audienceSize, preferredHashtags.
postnext://teams/current
Current team metadata (teamId, teamName) plus totalTeamsAvailable count. Pair with list_teams + set_current_team to switch.
Named workflows
Slash-menu shortcuts that orchestrate multiple tools. Pick one and let your AI assistant walk through a multi-step task with confirmation before mutating anything.
/weekly-plan
Read brand profile, connected channels and queued posts, then propose 5-10 drafts spread Monday to Friday.
/draft-thread
Write a 5-10 post Twitter or Threads thread on a topic or URL you supply, in your brand voice.
/audit-queue
Walk the next 7 days of scheduled posts and flag duplicates, platform overload, and gaps.
/channel-sweep
Compare connected channels against the supported set and suggest which to connect or refresh.
Discovery and OAuth endpoints
Standards-compliant metadata so any MCP client can auto-configure.
GET /.well-known/oauth-protected-resource/api— RFC 9728GET /.well-known/oauth-authorization-server— RFC 8414GET /oauth/.well-known/openid-configuration— OIDCPOST /oauth/reg— Dynamic Client Registration (RFC 7591)GET /oauth/auth— Authorize (PKCE-S256 required)POST /oauth/token— Token exchange
Open source
Paste-ready recipes and a developer-facing reference live in our public docs repo. MIT-licensed; pull requests welcome. github.com/postnextio/postnext-mcp
Run your social channels from any AI assistant
Plan, draft, schedule, and audit posts across 7 platforms by chatting with Claude, ChatGPT, or Cursor. One secure connection, every PostNext feature.