SocialCannon provides a REST API for publishing to 6 social platforms (Twitter, Facebook, Instagram, LinkedIn, TikTok, YouTube), analytics, engagement monitoring, A/B testing, AI-powered content repurposing, and 23 MCP tools for AI agents. OpenClaw ready.
curl -X POST https://socialcannon.app/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{ "grant_type": "client_credentials", "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_SECRET" }'curl -X POST https://socialcannon.app/api/v1/posts \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"accountId": "ACCOUNT_ID",
"content": "Hello from SocialCannon!"
}'The mediaUrls field accepts any public URL — you can use an image already hosted elsewhere, or upload via the media endpoint first.
Option A: Use an external URL directly
curl -X POST https://socialcannon.app/api/v1/posts \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"accountId": "ACCOUNT_ID",
"content": "Check out this photo!",
"mediaUrls": ["https://example.com/photo.jpg"]
}'Option B: Upload via signed URL (direct-to-GCS)
# Step 1: Ask for a signed PUT URL
curl -X POST https://socialcannon.app/api/v1/media/upload-init \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "filename": "photo.jpg", "contentType": "image/jpeg", "size": 123456 }'
# Returns: { "success": true, "data": { "uploadUrl": "...", "publicUrl": "...", "requiredHeaders": { ... } } }
# Step 2: PUT the bytes directly to GCS (file size up to 4GB)
curl -X PUT "<uploadUrl>" \
-H "Content-Type: image/jpeg" \
-H "x-goog-acl: public-read" \
-H "x-goog-content-length-range: 0,4294967296" \
-H "x-goog-if-generation-match: 0" \
-H "x-goog-meta-socialcannon-client-key: <copy exact value from requiredHeaders>" \
-H "x-goog-meta-socialcannon-upload-state: quota-reserved" \
--data-binary "@photo.jpg"
# Step 3: Finalize so usage counter increments and lifecycle clock starts
curl -X POST https://socialcannon.app/api/v1/media/upload-complete \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "publicUrl": "<publicUrl from step 1>" }'
# Step 4: Use the publicUrl when creating the post
curl -X POST https://socialcannon.app/api/v1/posts \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"accountId": "ACCOUNT_ID",
"content": "Check out this photo!",
"mediaUrls": ["<publicUrl>"]
}'Bytes go straight to Google Cloud Storage — SocialCannon never touches them, so files up to 4GB upload cleanly. Up to 4 images for Twitter, 10 for Instagram carousels.
curl -X POST https://socialcannon.app/api/v1/posts \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"accountId": "ACCOUNT_ID",
"content": "Scheduled post!",
"scheduledAt": "2026-04-01T10:00:00Z"
}'SocialCannon uses OAuth2 client_credentials flow. Exchange yourclient_id andclient_secret for a JWT token that expires in 1 hour.
Include the token in all authenticated requests:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...Available scopes:
Rate limiting:
Free: 30 requests/minute. Pro: 300 requests/minute. Returns 429 with Retry-After header when exceeded.
2 connected accounts
10 posts/period, 2 scheduled
1 Twitter/X post per period (hard cap)
All 6 platforms
No analytics, no replies
2-item threads, 5 tracked links
3 media uploads/period
30 API requests/min
Unlimited accounts & posts
50 Twitter/X posts per period
Full analytics with history
25-item threads, unlimited tracked links
Engagement replies, A/B testing (5×4)
Timing suggestions, 90-day calendar
300 API requests/min
Everything in Pro
250 Twitter/X posts per period
A/B testing (20 concurrent, 6 variants)
365-day calendar
Priority support
600 API requests/min
Custom Twitter/X quota
Custom rate limits + SLA
Dedicated support
Custom integrations
1200 API requests/min default
{
"success": true,
"data": { ... }
}{
"success": false,
"error": "Description of the error",
"code": "LIMIT_EXCEEDED",
"details": { ... }
}Getting started — publish your first postPublic1. Get your credentials from the dashboard Settings page (a client is auto-provisioned on signup): the client_id is always shown; the client_secret is revealed once — click "Generate API Secret" to issue a new one, which revokes the old secret 2. Get a token: POST /api/v1/auth/token with your credentials 3. Connect a social account: POST /api/v1/accounts/connect-token with the platform, then open the returned connectUrl in a browser (dashboard users just click Connect — their session authorizes the link) 4. List connected accounts: GET /api/v1/accounts (use the account id from the response) 5. Publish a post: POST /api/v1/posts with accountId, content, and optional mediaUrls
// Example: publish a text post to a connected account
POST /api/v1/posts
Authorization: Bearer YOUR_TOKEN
{
"accountId": "your-account-id",
"content": "Hello from SocialCannon!"
}
// Response:
{
"success": true,
"data": {
"id": "post-id",
"status": "published",
"platformPostId": "...",
"platformPostUrl": "https://..."
}
}Platform capabilitiesPublicEach platform supports different content types. Use the mediaType field in platformOptions to control how content is published.
Facebook:
• Feed post (text, images, or video)
• Story (single image or video, mediaType: "story")
• Reel (single video, mediaType: "reel")
Instagram:
• Feed post (images required, up to 10 for carousel)
• Story (single image or video, mediaType: "story")
• Reel (single video, mediaType: "reel")
Twitter/X:
• Tweet (text, optional images or video)
TikTok:
• Video post (single video required)
• Photo carousel (multiple images, up to 35)
• Story (single image or video, mediaType: "story")
• privacyLevel required on all posts (use get_tiktok_creator_info to get allowed values)
YouTube:
• Video upload (single video, mediaType: "feed")
• Short (single vertical video ≤60s, mediaType: "short")
• Community post (text only, mediaType: "community")/api/v1/auth/tokenPublicExchange client credentials for a JWT access token (OAuth2 client_credentials flow).
{ "grant_type": "client_credentials", "client_id": "...", "client_secret": "sc_..." }{ "success": true, "data": { "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600, "scope": "posts:read posts:write ..." } }/api/v1/auth/identityAttest the active bearer token's own client ID and creation-time client name. Returns no secret, scopes, trust flag, or tenant metadata.
{ "success": true, "data": { "identityContractVersion": 1, "client_id": "...", "name": "My Client" } }/api/v1/auth/clientsList all API clients.
/api/v1/auth/clientsCreate a new API client. Returns the secret once.
{ "name": "My Client", "scopes": ["posts:read", "posts:write"] }/api/v1/postsList posts. Supports cursor-based pagination.
/api/v1/postsCreate a post. Omit scheduledAt to publish immediately. Media URLs must be publicly accessible. Maximum scheduling window: 30 days. An optional Idempotency-Key header (8-128 printable ASCII characters, no spaces) binds one canonical request to one durable post ID; exact replays never publish again and set Idempotency-Replayed: true. A different request under the same key returns 409 IDEMPOTENCY_CONFLICT. In-progress or provider-uncertain replays return 409 with the stable data.id and must be recovered, never compensated as a new post. Set platformOptions.aiGenerated: true to request the platform's native AI-content label. TikTok and YouTube support it; Instagram requests fail validation unless the deployment has explicitly enabled verified native-disclosure access — see the platformOptions reference.
{
"accountId": "abc123",
"content": "Hello world!",
"mediaUrls": ["https://example.com/video.mp4"],
"scheduledAt": "2026-04-01T10:00:00Z",
"platformOptions": {
"mediaType": "reel",
"aiGenerated": true
}
}// Success:
{ "success": true, "data": { "id": "...", "status": "published", "platformPostId": "...", "platformPostUrl": "..." } }
// Provider acknowledged, local persistence failed (HTTP 502):
{ "success": false, "error": "...", "data": { "id": "...", "platformPostId": "...", "platformPostUrl": "...", "providerPublished": true, "reconciliationRequired": true } }
// Exact keyed replay still in progress (HTTP 409; never delete/redispatch this ID):
{ "success": false, "error": "...", "code": "IDEMPOTENCY_IN_PROGRESS", "data": { "id": "...", "status": "draft" } }/api/v1/posts/idempotencyRecover the public state of a keyed POST /api/v1/posts operation. Send the original Idempotency-Key in the request header, not the URL. A 404 means no durable binding exists at that read; another same-key request can still be in preflight.
// Found:
{ "success": true, "data": { "id": "...", "status": "failed", "platformPostId": "...", "platformPostUrl": "...", "recovery": { "required": true, "code": "PROVIDER_RECONCILIATION_REQUIRED", "kind": "publish_outcome" } } }
// Not found (HTTP 404):
{ "success": false, "error": "No post was found for this Idempotency-Key", "code": "IDEMPOTENCY_NOT_FOUND" }/api/v1/posts/:idGet a single post by ID.
/api/v1/posts/:idUpdate a draft or scheduled post.
{ "content": "Updated text", "scheduledAt": "..." }/api/v1/posts/:idDelete a post. Every delete now fails closed when the record contains provider-publish evidence: SocialCannon must verify any required provider deletion before removing the local record. For rollback/reconciliation, send { strictCompensation: true, platformPostId? }; the optional provider ID must exactly match persisted evidence. Unsupported or failed provider deletion returns a non-2xx response and leaves the local record available for operator reconciliation. A post with an uncertain publish attempt and no provider ID cannot be deleted until it is reconciled.
{ "strictCompensation": true, "platformPostId?": "exact provider post id" }/api/v1/posts/threadCreate a multi-part thread (Twitter thread or Instagram carousel). Publishes immediately or schedules for later. Maximum scheduling window: 30 days. Accepts platformOptions, including aiGenerated: true. Instagram rejects that request before publishing unless verified native-disclosure access is enabled for the deployment.
{
"accountId": "abc123",
"items": [
{ "content": "Thread part 1..." },
{ "content": "Thread part 2...", "mediaUrls": ["https://..."] }
],
"scheduledAt": "2026-04-01T10:00:00Z"
}/api/v1/accountsList all connected social media accounts.
/api/v1/accounts/:idGet a single account. Tokens are stripped from the response.
/api/v1/accounts/:idDisconnect a social account.
/api/v1/accounts/:id/tiktok/creator-infoTikTok only. Returns the account's live posting capabilities for building a compliant post UI: allowed privacy levels (`privacyLevelOptions`), whether Comment/Duet/Stitch are disabled, the creator nickname/username, and `maxVideoPostDurationSec`.
{ "success": true, "data": { "creatorNickname": "...", "creatorUsername": "...", "creatorAvatarUrl": "...", "privacyLevelOptions": ["PUBLIC_TO_EVERYONE","SELF_ONLY"], "commentDisabled": false, "duetDisabled": false, "stitchDisabled": false, "maxVideoPostDurationSec": 600 } }/api/v1/media/upload-initStep 1 — initialize a direct-to-GCS upload. Returns a 15-minute V4 signed PUT URL plus the final public URL. Enforces subscription quota before issuing the URL.
{ "filename": "clip.mp4", "contentType": "video/mp4", "size": 12345678 }{ "data": { "uploadUrl": "https://storage.googleapis.com/...", "publicUrl": "https://storage.googleapis.com/...", "requiredHeaders": { "Content-Type": "video/mp4", "x-goog-acl": "public-read", "x-goog-content-length-range": "0,4294967296", "x-goog-if-generation-match": "0", "x-goog-meta-socialcannon-client-key": "...", "x-goog-meta-socialcannon-upload-state": "quota-reserved" } } }{uploadUrl}PublicStep 2 — upload the file bytes directly to GCS using the signed URL from step 1. Must include the headers from `requiredHeaders` exactly or the signature check fails. Supports files up to 4GB.
Raw binary file contents200 OK (no body)/api/v1/media/upload-completeStep 3 — finalize the upload: stamps the lifecycle retention timer (customTime) and increments your `mediaUploadsThisPeriod` counter. Verifies the object exists in your client prefix before bookkeeping.
{ "publicUrl": "https://storage.googleapis.com/.../media/{clientId}/{uuid}.{ext}" }{ "data": { "url": "...", "filename": "...", "contentType": "...", "size": 102400 } }/api/v1/calendarGet a content calendar view with posts, gap analysis, and summary stats for a date range (max 90 days).
{ "posts": [...], "summary": { "totalPosts", "postsByPlatform", "postsByDay", "gaps": [{ "date", "dayOfWeek" }] } }/api/v1/calendar/slotsFind available time slots not occupied by scheduled or published posts (max 14-day range).
{ "slots": [{ "start", "end", "available" }], "totalSlots", "availableSlots", "occupiedSlots" }/api/v1/posts/:id/analyticsFetch live engagement metrics from the platform, store a snapshot, and return current + history.
{ "current": { "metrics": { "likes", "comments", "shares", "impressions", ... }, "fetchedAt" }, "history": [...] }/api/v1/analytics/summaryGet aggregate analytics across all posts for a date range.
{ "totalPosts", "totalLikes", "totalComments", "totalShares", "totalImpressions", "avgEngagementRate" }/api/v1/analytics/refreshBulk-refresh analytics for multiple posts (max 50 per request).
{ "postIds?": ["..."], "platform?": "twitter", "limit?": 20 }{ "refreshed": 15, "failed": 2, "results": [{ "postId", "success", "error?" }] }/api/v1/linksList tracked links with UTM parameters. Supports cursor-based pagination.
/api/v1/links/generateGenerate a UTM-tagged URL. Optionally save it for tracking.
{ "url": "https://example.com", "platform?": "twitter", "campaign?": "launch", "content?": "cta-btn", "postId?": "...", "save?": true }{ "originalUrl": "https://example.com", "trackedUrl": "https://example.com?utm_source=twitter&...", "linkId?": "..." }/api/v1/posts/:id/engagementsFetch comments and replies from the platform for a published post. Stores new engagements.
{ "engagements": [{ "type", "authorName", "content", "createdAt", ... }], "nextCursor?" }/api/v1/engagementsList all stored engagements across posts. Filter by read status for an inbox view.
/api/v1/engagements/:idMark an engagement as read.
/api/v1/engagements/:id/replyReply to a comment or mention directly on the social platform.
{ "content": "Thanks for the feedback!" }{ "platformReplyId": "...", "engagementId": "...", "repliedAt": "..." }/api/v1/posts/repurposeUse AI to adapt content for multiple platforms. Two modes: "preview" returns adapted variants for review, "post" adapts and publishes in one call. Content is humanized to remove AI writing patterns. Trusted clients bypass tier limits.
{
"sourceContent": "Your original text...",
"targetPlatforms": ["twitter", "facebook", "tiktok"],
"mode?": "preview",
"tone?": "professional",
"accountIds?": { "twitter": "acc_1", "facebook": "acc_2" },
"mediaUrls?": { "twitter": ["https://..."] },
"appendContent?": { "twitter": "Extra text for Twitter" },
"appendToAll?": "Appended to all platforms"
}preview: { "variants": [{ "platform", "content", "validation", "characterCount" }], "allValid" }
post: { "results": [{ "platform", "success": true, "postId", "postUrl?", "platformPostId?" } | { "platform", "success": false, "postId?", "platformPostId?", "providerPublished?", "reconciliationRequired?", "error" }] }
Every created post-mode result includes its SocialCannon postId, including failed sandbox dry runs. If providerPublished is true, retain platformPostId and use strict compensation or operator reconciliation./api/v1/ab-testsCreate an A/B test. Requires an Idempotency-Key header (8-128 printable ASCII characters, no spaces); reuse the exact key after a timeout or lost response. The test row, deterministic variant post rows, usage reservations, and active-test slot are provisioned atomically before scheduler or provider visibility. If scheduledAt is omitted, the pre-provisioned variants publish immediately through provider mutation fences; if scheduledAt is provided, all variants are scheduled atomically for that time (within 30 days). A new operation returns 201; an exact committed replay returns 200 with replayed: true; the same key with a different payload returns 409 IDEMPOTENCY_CONFLICT. Immediate uncertain outcomes retain the test in provisioning with exact post IDs and return 502 for reconciliation; an operator-confirmed absent outcome is consumed once on same-key replay before untouched variants resume. Definitive failed variants are excluded from comparison. Once all variants settle, at least two published variants activate the test; otherwise it becomes failed with terminalReason insufficient_eligible_variants and transactionally releases its active slot. Scheduled tests follow the same terminal rule. Test duration starts at the latest durable eligible-variant publish, and completion waits for analytics on every eligible variant. Per-variant mediaUrls optional. Not supported for TikTok accounts (A/B variants cannot set the privacy level TikTok requires) or platforms whose post analytics are unavailable. Set aiGenerated: true to request the native label on every variant. Instagram rejects that request before publishing unless verified native-disclosure access is enabled for the deployment.
Header: Idempotency-Key: ab-test-launch-2026-04-20
{
"accountId": "abc123",
"name": "CTA test",
"variants": [
{ "content": "Check out our new feature!", "mediaUrls": ["https://..."] },
{ "content": "You won't believe this new feature..." }
],
"metric?": "engagementRate | likes | impressions | clicks",
"minDurationHours?": 24,
"scheduledAt?": "2026-04-20T10:00:00Z",
"aiGenerated?": true
}201 new / 200 replay: { "success": true, "replayed?": true, "data": { "id": "...", "status": "provisioning | active | failed", "startedAt?": "...", "terminalReason?": "insufficient_eligible_variants", "variants": [{ "id", "postId", "content", "state", "platformPostId?" }], ... } }
409: { "success": false, "code": "IDEMPOTENCY_CONFLICT | SCHEDULE_EXPIRED", "error": "..." }
502 partial/reconciliation: { "success": false, "code?": "PARTIAL_FAILURE | PROVIDER_RECONCILIATION_REQUIRED | INSUFFICIENT_ELIGIBLE_VARIANTS", "error": "...", "data": { "id", "status": "provisioning | active | failed", "variants": [{ "id", "postId", "state", "platformPostId?" }] } }/api/v1/ab-testsList A/B tests. Filter by status.
/api/v1/ab-tests/:idGet test details with live per-variant analytics and current winner.
{ "variants", "variantResults": [{ "variantId", "metrics" }], "currentWinner", "confidence" }/api/v1/ab-tests/:id/completeForce-complete an active test only after at least two eligible variants are durably published and each has analytics; atomically releases its active-test slot.
/api/v1/ab-tests/:idCancel an active or provisioning test and atomically release its active-test slot. Variant posts remain as regular posts.
/api/v1/accounts/:id/timingAnalyze posting history and engagement to recommend optimal days and hours for posting.
{ "suggestions": [{ "day", "hour", "engagementRate", "sampleSize" }], "timezone" }/api/v1/platformsPublicList all supported platforms and their capabilities. Public endpoint, no auth required.
sandbox platformPublicThe sandbox platform is a dry-run test platform — nothing is published anywhere. Create a post with accountId: "sandbox" and a sandbox account is auto-provisioned on first use (no OAuth, no tokens). Publish returns a simulated success (fake sandbox_* post ID and URL). Sandbox posts do not consume your monthly post quota and work on every plan, so you can try the full API — create, publish, schedule, MCP tools — before connecting a real account. Sandbox usage is bounded by a lifetime allowance of 100 dry runs per account; media attachments are accepted but discarded, and auto-UTM is skipped.
platformOptions referencePublicThe platformOptions field in POST /api/v1/posts accepts platform-specific options. Use mediaType to control how content is published.
mediaType values:
• "reel" — Facebook or Instagram Reel (single video, MP4/MOV)
• "story" — Facebook, Instagram, or TikTok Story (single media, 24h ephemeral)
• "short" — YouTube Short (single video, vertical ≤60s)
• "community" — YouTube Community tab post (text only)
Media rules:
• Stories, Reels, and Shorts accept only one media attachment
• Facebook/Instagram feed posts accept multiple images (carousel, max 10)
• TikTok photo carousel accepts up to 35 images
• TikTok video posts accept only one video
• YouTube videos/shorts accept only one video file
• All media URLs must be publicly accessible
YouTube-specific options:
• "title" — Video title (max 100 chars)
• "tags" — Comma-separated video tags
• "categoryId" — YouTube category ID (default: "10" Music)
• "privacyStatus" — "public", "unlisted", or "private"
TikTok-specific options (call GET /api/v1/accounts/:id/tiktok/creator-info first):
• "privacyLevel" — REQUIRED. One of: PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY. Choose from privacyLevelOptions returned by creator-info. Omitting this field returns 400 TIKTOK_PRIVACY_REQUIRED.
• "disableComment" — boolean (default: false). Set true to disable comments. Must be true if commentDisabled is true on creator-info.
• "disableDuet" — boolean (default: false). Video only. Set true to disable duets. Must be true if duetDisabled is true on creator-info.
• "disableStitch" — boolean (default: false). Video only. Set true to disable stitches. Must be true if stitchDisabled is true on creator-info.
• "commercialContent" — boolean. Set true if the post promotes a brand, product, or service (enables commercial content disclosure).
• "brandOrganic" — boolean. Set true if promoting your own brand ("Your Brand" disclosure). Requires commercialContent: true.
• "brandedContent" — boolean. Set true for paid/third-party partnerships. Requires commercialContent: true. Cannot be combined with privacyLevel: SELF_ONLY.
AI-generated content disclosure (cross-platform):
• "aiGenerated" — boolean (default: false). Set true when the post's media is AI-generated. SocialCannon maps it to the platform's native AI-content label:
- Instagram: fails validation by default because native disclosure is not yet enabled for this deployment. When verified access is explicitly enabled, sends is_ai_generated on the media container (carousel parent only)
- TikTok: is_aigc ("AI-generated" label)
- YouTube: containsSyntheticMedia ("Altered content" disclosure)
Ignored on platforms without a disclosure field (Facebook, X, LinkedIn).
Also accepted by POST /api/v1/posts/thread (platformOptions), per-post in POST /api/v1/posts/auto-schedule (posts[].platformOptions), and as a test-level "aiGenerated" flag on POST /api/v1/ab-tests./api/v1/accounts/connect-tokenMint a short-lived (10 min) connect token that authorizes one OAuth connect flow for your client. Required scope: accounts:write. Returns a ready-to-open connectUrl — send the user there in a browser.
{ "platform": "twitter", "redirectTo": "/dashboard/accounts" }{ "data": { "connectUrl": "https://…/api/connect/twitter?connect_token=…", "expiresIn": 600 } }/api/connect/:platformPublicInitiate OAuth connection for a social platform. Redirects to the platform's authorization page. Supported platforms: twitter, facebook, instagram, linkedin, tiktok, youtube. Caller must prove tenancy: either a signed-in dashboard session (the account is linked to the session's client) or a connect_token minted via POST /api/v1/accounts/connect-token. A bare client_id is not accepted.
/api/connect/:platform/callbackPublicOAuth callback handler. Exchanges authorization code for tokens and stores the connected account. Do not call directly — the platform redirects here after authorization.
Scope referencePublicScopes control what an API client can access. Assign scopes when creating a client via POST /api/v1/auth/clients.
posts:read — List and view posts
posts:write — Create, update, schedule posts
posts:delete — Delete posts
accounts:read — List connected accounts
accounts:write — Connect/disconnect social accounts
media:upload — Upload media files
analytics:read — View analytics and timing suggestions
engagements:read — View comments and replies
engagements:write — Reply to engagements, mark as read
links:read — View tracked links
links:write — Generate UTM-tagged links
clients:manage — Create and list API clientsSocial accounts are connected via browser-based OAuth flows — this step requires a human to log in to the platform, so it cannot be completed by an unattended agent. Each connected account also carries a tokenHealth field; skip or reconnect any account whose token is expired before publishing. Redirect users to the connect endpoint for the desired platform:
| Platform | Connect URL | Notes |
|---|---|---|
| Twitter / X | /api/connect/twitter?connect_token=... | OAuth2 with PKCE |
/api/connect/facebook?connect_token=... | Page-level access | |
/api/connect/instagram?connect_token=... | Requires media (no text-only) | |
/api/connect/linkedin?connect_token=... | Post-only (no analytics/engagement) |
After authorization, the user is redirected back to your app with the account connected. Tokens are encrypted with AES-256-GCM before storage.
scheduledAt → immediate publishscheduledAt → the requested time is preserved without rounding (up to 30 days ahead)280 character limit
Up to 4 images per tweet
Threads via reply chains
v2 API for tweets, v1.1 for media upload
63,206 character limit
Page-level access tokens
Supports native scheduling via API
Analytics and engagement pending Meta review
2,200 character limit
Requires media (no text-only posts)
Max 10 carousel items, no API deletion
Threads use carousel container model
3,000 character limit
Text-only or single/multi-image posts
Threads combine items into one post
Post-only (no analytics or engagement)
SocialCannon includes an MCP server with 23 tools that expose all functionality for AI agents (Claude, GPT, etc.) via the @modelcontextprotocol/sdk over stdio transport.
Start the MCP server (repo):
npm run mcpOr run the published package directly (external clients):
npx -y @socialcannon/mcpSet SOCIALCANNON_CLIENT_ID and SOCIALCANNON_CLIENT_SECRET (from your dashboard) before running.
Configuration (preferred — auto-refreshes JWT):
MCP_CLIENT_ID=<your API client ID>
MCP_CLIENT_SECRET=<your API client secret>
SOCIALCANNON_API_URL=https://socialcannon.appAlternatively, set MCP_API_TOKEN with a pre-generated JWT (expires after 1 hour, no auto-refresh).
List all supported social media platforms and their capabilities.
Params: None
List all connected social media accounts.
Params: platform?
Fetch a TikTok account's posting capabilities — allowed privacy levels, disabled interactions, nickname, max video duration. Call before create_post for TikTok.
Params: accountId
Create and publish or schedule a social media post. For TikTok, privacyLevel is required (from get_tiktok_creator_info); also supports disableComment, disableDuet (video), disableStitch (video), commercialContent, brandOrganic, brandedContent. Set aiGenerated to request the native label on TikTok or YouTube. Instagram rejects that request unless verified native-disclosure access is enabled for the deployment.
Params: accountId, content, mediaUrls?, scheduledAt?, visibility?, privacyLevel?, disableComment?, disableDuet?, disableStitch?, commercialContent?, brandOrganic?, brandedContent?, aiGenerated?
List social media posts with optional filters.
Params: status?, platform?, accountId?, limit?
Get details of a specific post.
Params: postId
Update a draft or scheduled post.
Params: postId, content?, scheduledAt?
Delete a post with provider-safe compensation. Provider-bound and reconciliation rows require strictCompensation; platformPostId may be supplied only as an exact persisted-ID check.
Params: postId, strictCompensation?, platformPostId?
Repair or retry a failed single post. Provider-confirmed rows are repaired locally with no republish; uncertain rows reconcile or fail closed. Failed threads without a provider ID require operator reconciliation or creation of a new thread.
Params: postId
Get a content calendar view with posts, gaps, and summary stats.
Params: startDate, endDate, accountId?, platform?
Get engagement metrics (likes, comments, shares, impressions) for a post.
Params: postId
Get aggregate analytics across all posts for a date range.
Params: startDate, endDate, platform?, accountId?
Create a multi-part thread (Twitter thread or Instagram carousel). Instagram aiGenerated requests fail closed unless verified native-disclosure access is enabled.
Params: accountId, items[], scheduledAt?, aiGenerated?
Generate a UTM-tagged URL for tracking social media traffic.
Params: url, platform?, campaign?, content?, postId?, save?
List stored engagements (comments/replies) with optional filters.
Params: postId?, isRead?, limit?, cursor?
Reply to a comment/reply directly on the social platform.
Params: engagementId, content
List all unread engagements (inbox shortcut).
Params: limit?
Repurpose content for different platforms using AI. Supports preview mode (returns variants) and post mode (adapts and publishes). Content is humanized automatically.
Params: sourceContent, targetPlatforms[], mode?, tone?, accountIds?, mediaUrls?, appendContent?, appendToAll?
Atomically create an idempotent A/B test with content variants for an account. Reuse the exact key after a timeout.
Params: idempotencyKey, accountId, name, variants[], metric?, minDurationHours?
Get A/B test results with per-variant analytics and winner.
Params: testId
Get recommended posting times based on historical engagement data.
Params: accountId, timezone?
Find the best available time slot for a new post based on calendar gaps and engagement data.
Params: accountId, startDate?, endDate?
Idempotently fill a week with optimally timed posts. A durable manifest preserves deterministic post IDs, slots, quota reservations, and partial outcomes across same-key retries. Each post can request a native AI-content label; Instagram requests fail closed unless verified native-disclosure access is enabled.
Params: idempotencyKey, accountId, posts[], timezone?
Hermes Agent (Nous Research) ships a first-class MCP client. Point it at the published @socialcannon/mcp package and all 23 tools load over npx — no repo clone.
mcp_servers:
socialcannon:
command: "npx"
args: ["-y", "@socialcannon/mcp"]
env:
SOCIALCANNON_CLIENT_ID: "your-client-id"
SOCIALCANNON_CLIENT_SECRET: "your-client-secret"Use the SOCIALCANNON_CLIENT_ID / SOCIALCANNON_CLIENT_SECRET from your dashboard. Run /reload-mcp in your Hermes session and the tools load automatically (Hermes also auto-reloads config edits within ~30s). Other MCP hosts share the same command, args, and env — Claude Desktop uses an equivalent JSON block (mcpServers) in claude_desktop_config.json.
SocialCannon is available as an OpenClaw skill. Install it from ClawHub so your AI assistant can publish, schedule, analyze, and manage social media posts through natural conversation.
openclaw skills install socialcannonAdd your SocialCannon API credentials to your openclaw.json config:
{
"skills": {
"entries": {
"socialcannon": {
"enabled": true,
"env": {
"SOCIALCANNON_CLIENT_ID": "your-client-id",
"SOCIALCANNON_CLIENT_SECRET": "your-client-secret"
}
}
}
}
}If you prefer manual installation, create this file at ~/.openclaw/workspace/skills/socialcannon/SKILL.md:
---
name: socialcannon
description: >
Publish, schedule, and manage social media posts across
Twitter/X, Facebook, Instagram, LinkedIn, TikTok, and YouTube.
Includes content calendar, analytics, A/B testing,
engagement inbox, AI content repurposing, and timing suggestions.
version: 1.0.0
metadata:
openclaw:
requires:
env:
- SOCIALCANNON_CLIENT_ID
- SOCIALCANNON_CLIENT_SECRET
bins:
- curl
primaryEnv: SOCIALCANNON_CLIENT_ID
emoji: "\U0001F4E3"
homepage: https://socialcannon.app
---
# SocialCannon
Social media publishing API with 23 MCP tools.
## Authentication
First, get a JWT token:
```bash
curl -X POST https://socialcannon.app/api/v1/auth/token \
-H "Content-Type: application/json" \
-d "{
\"grant_type\": \"client_credentials\",
\"client_id\": \"$SOCIALCANNON_CLIENT_ID\",
\"client_secret\": \"$SOCIALCANNON_CLIENT_SECRET\"
}"
```
Use `response.data.access_token` (responses are wrapped in `{ success, data }`) as a Bearer token for all
subsequent requests. Tokens expire after 1 hour — request a new one when you get a 401.
## Core Operations
- POST /api/v1/posts — Create/publish a post
- GET /api/v1/posts — List posts (cursor pagination)
- POST /api/v1/posts/thread — Create thread/carousel
- GET /api/v1/calendar — Content calendar with gaps
- GET /api/v1/posts/:id/analytics — Engagement metrics
- GET /api/v1/engagements — Comment inbox
- POST /api/v1/engagements/:id/reply — Reply to comment
- POST /api/v1/posts/repurpose — AI content adaptation
- POST /api/v1/ab-tests — Create A/B test (requires stable Idempotency-Key header)
- GET /api/v1/accounts/:id/timing — Best posting times
- POST /api/v1/links/generate — UTM-tagged URLCreate your first API client:
npx tsx scripts/create-client.ts "My App"This writes directly to Firestore and prints the client_id andclient_secret. Save the secret — it's only shown once.
Or use the dashboard:
Sign in with Google or GitHub at /sign-in and an API client is automatically provisioned for your account.
Stuck on something? Reach out at support@socialcannon.app