Openclaw Skill
Schedule and manage social media posts across TikTok, Instagram, Facebook, X (Twitter), YouTube, LinkedIn, Threads, Bluesky, Pinterest, Telegram, and Google...
Schedule and manage social media posts across TikTok, Instagram, Facebook, X (Twitter), YouTube, LinkedIn, Threads, Bluesky, Pinterest, Telegram, and Google...
Real data. Real impact.
Growing
Developers
Per week
Open source
Skills give you superpowers. Install in 30 seconds.
Schedule social media posts across 11 platforms from one API. SaaS — no self-hosting needed.
export POSTFAST_API_KEY="your-api-key"
Base URL:
https://api.postfa.st
Auth header: pf-api-key: $POSTFAST_API_KEY
Important: The header name is
pf-api-key (not Authorization: Bearer or x-api-key). Regenerating your key in settings permanently invalidates the previous one. See Troubleshooting if you get 403 errors.
curl -s -H "pf-api-key: $POSTFAST_API_KEY" https://api.postfa.st/social-media/my-social-accounts
Returns array of
{ id, platform, platformUsername, displayName }. Save the id — it's the socialMediaId required for every post.
Platform values:
TIKTOK, INSTAGRAM, FACEBOOK, X, YOUTUBE, LINKEDIN, THREADS, BLUESKY, PINTEREST, TELEGRAM, GOOGLE_BUSINESS_PROFILE
curl -X POST https://api.postfa.st/social-posts \ -H "pf-api-key: $POSTFAST_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "posts": [{ "content": "Your post text here", "mediaItems": [], "scheduledAt": "2026-06-15T10:00:00.000Z", "socialMediaId": "ACCOUNT_ID_HERE" }], "controls": {} }'
Returns
{ "postIds": ["uuid-1"] }.
Step A — Get signed upload URLs:
curl -X POST https://api.postfa.st/file/get-signed-upload-urls \ -H "pf-api-key: $POSTFAST_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contentType": "image/png", "count": 1 }'
Returns
[{ "key": "image/uuid.png", "signedUrl": "https://..." }].
Step B — Upload file to S3:
curl -X PUT "SIGNED_URL_HERE" \ -H "Content-Type: image/png" \ --data-binary @/path/to/file.png
Step C — Create post with media key:
curl -X POST https://api.postfa.st/social-posts \ -H "pf-api-key: $POSTFAST_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "posts": [{ "content": "Post with image!", "mediaItems": [{ "key": "image/uuid.png", "type": "IMAGE", "sortOrder": 0 }], "scheduledAt": "2026-06-15T10:00:00.000Z", "socialMediaId": "ACCOUNT_ID_HERE" }], "controls": {} }'
For video: use
contentType: "video/mp4", type: "VIDEO", key prefix video/.
curl -s -H "pf-api-key: $POSTFAST_API_KEY" "https://api.postfa.st/social-posts?page=0&limit=20"
Returns
{ "data": [...], "totalCount": 25, "pageInfo": { "page": 1, "hasNextPage": true, "perPage": 20 } }.
Query parameters:
page (int, default 0) — 0-based page index. Response shows 1-based display page in pageInfo.pagelimit (int, default 20, max 50) — items per pageplatforms (string) — comma-separated filter: FACEBOOK,INSTAGRAM,Xstatuses (string) — comma-separated: DRAFT, SCHEDULED, PUBLISHED, FAILEDfrom / to (ISO 8601 UTC) — date range filter on scheduledAtExample:
GET /social-posts?page=0&limit=50&platforms=X,LINKEDIN&statuses=SCHEDULED&from=2026-06-01T00:00:00Z&to=2026-06-30T23:59:59Z
curl -X DELETE -H "pf-api-key: $POSTFAST_API_KEY" https://api.postfa.st/social-posts/POST_ID
Include multiple entries in the
posts array, each with a different socialMediaId. They share the same controls and mediaItems keys.
Let clients connect their social accounts to your workspace without creating a PostFast account:
curl -X POST https://api.postfa.st/social-media/connect-link \ -H "pf-api-key: $POSTFAST_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "expiryDays": 7, "sendEmail": true, "email": "client@example.com" }'
Returns
{ "connectUrl": "https://app.postfa.st/connect?token=..." }. Share the URL — they can connect accounts directly. Rate limit: 50/hour.
Omit
scheduledAt and set status: "DRAFT" to save without scheduling:
curl -X POST https://api.postfa.st/social-posts \ -H "pf-api-key: $POSTFAST_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "posts": [{ "content": "Draft idea...", "mediaItems": [], "socialMediaId": "ACCOUNT_ID" }], "status": "DRAFT", "controls": {} }'
Fetch published posts with their performance metrics:
curl -s -H "pf-api-key: $POSTFAST_API_KEY" \ "https://api.postfa.st/social-posts/analytics?startDate=2026-03-01T00:00:00.000Z&endDate=2026-03-31T23:59:59.999Z&platforms=TIKTOK,INSTAGRAM"
Query parameters:
startDate (ISO 8601, required) — start of date rangeendDate (ISO 8601, required) — end of date rangeplatforms (string, optional) — comma-separated filtersocialMediaIds (string, optional) — comma-separated account UUIDsReturns
{ "data": [{ id, content, socialMediaId, platformPostId, publishedAt, latestMetric }] }.
latestMetric fields: impressions, reach, likes, comments, shares, totalInteractions, fetchedAt, extras. All numbers are strings (bigint). latestMetric is null if metrics haven't been fetched yet.
Supported platforms for analytics: Facebook, Instagram, Threads, LinkedIn, TikTok, YouTube. LinkedIn personal accounts are excluded. YouTube returns views, likes, comments, and total interactions (no reach or shares).
Rate limit: 350/hour.
Post the same content to LinkedIn, X, and Threads at the same time:
curl -X POST https://api.postfa.st/social-posts \ -H "pf-api-key: $POSTFAST_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "posts": [ { "content": "Big announcement!", "mediaItems": [], "scheduledAt": "2026-06-15T09:00:00.000Z", "socialMediaId": "LINKEDIN_ID" }, { "content": "Big announcement!", "mediaItems": [], "scheduledAt": "2026-06-15T09:00:00.000Z", "socialMediaId": "X_ID" }, { "content": "Big announcement!", "mediaItems": [], "scheduledAt": "2026-06-15T09:00:00.000Z", "socialMediaId": "THREADS_ID" } ], "controls": {} }'
See examples/cross-platform-post.json for a complete example.
contentType: "video/mp4"instagramPublishType: "REEL"Optional: add a custom cover image by uploading a JPEG (max 8MB) via the same 3-step flow, then set
coverImageKey in the media item. You can also set coverTimestamp (milliseconds) as a fallback frame.
See examples/instagram-reel.json for the basic request, or examples/instagram-reel-cover.json for a Reel with a custom cover image.
Upload video, then post with privacy controls:
# controls object: { "tiktokPrivacy": "PUBLIC", "tiktokAllowComments": true, "tiktokAllowDuet": false, "tiktokAllowStitch": false, "tiktokBrandContent": true }
See examples/tiktok-video.json.
Always fetch boards first, then post:
# Step 1: Get boards curl -s -H "pf-api-key: $POSTFAST_API_KEY" \ https://api.postfa.st/social-media/PINTEREST_ACCOUNT_ID/pinterest-boardsStep 2: Post with board ID
controls: { "pinterestBoardId": "BOARD_ID", "pinterestLink": "https://yoursite.com" }
See examples/pinterest-pin.json.
Upload video, then post with YouTube controls:
# controls object: { "youtubeIsShort": true, "youtubeTitle": "Quick Tip: Batch Your Content", "youtubePrivacy": "PUBLIC", "youtubePlaylistId": "PLxxxxxx", "youtubeTags": ["tips", "productivity", "social media"], "youtubeMadeForKids": false }
See examples/youtube-short.json.
Upload both video and thumbnail image, then reference the thumbnail key in controls:
# 1. Upload thumbnail image (separate from video upload) curl -X POST https://api.postfa.st/file/get-signed-upload-urls \ -H "pf-api-key: $POSTFAST_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contentType": "image/jpeg", "count": 1 }' # PUT thumbnail to signed URL2. Upload video (same 3-step flow as always)
3. Create post with thumbnail key in controls:
{ "youtubeIsShort": false, "youtubeTitle": "Full Tutorial: Social Media Strategy", "youtubePrivacy": "PUBLIC", "youtubeThumbnailKey": "image/abc123.jpg", "youtubeTags": ["tutorial", "social media"], "youtubeMadeForKids": false }
Thumbnail specs: JPEG/PNG recommended, max 2MB, 1280x720 (16:9), min width 640px. Requires phone-verified YouTube channel. If thumbnail upload fails, the video still publishes without the custom thumbnail.
See examples/youtube-video-thumbnail.json.
Always fetch locations first, then post with GBP-specific controls:
# Step 1: Get locations curl -s -H "pf-api-key: $POSTFAST_API_KEY" \ https://api.postfa.st/social-media/GBP_ACCOUNT_ID/gbp-locationsStep 2: Create a standard post with CTA
controls: { "gbpLocationId": "accounts/.../locations/...", "gbpTopicType": "STANDARD", "gbpCallToActionType": "LEARN_MORE", "gbpCallToActionUrl": "https://yoursite.com" }
Three post types:
STANDARD (updates), EVENT (time-bound), OFFER (deals with coupons). EVENT and OFFER require gbpEventTitle, gbpEventStartDate, gbpEventEndDate.
See examples/gbp-standard.json, examples/gbp-event.json, and examples/gbp-offer.json.
Documents (PDF, PPTX, DOCX) display as swipeable carousels on LinkedIn.
contentType: "application/pdf"linkedinAttachmentKey instead of mediaItems# controls: { "linkedinAttachmentKey": "file/uuid.pdf", "linkedinAttachmentTitle": "Q1 Marketing Playbook" } # Note: mediaItems should be [] when using linkedinAttachmentKey
See examples/linkedin-document.json.
Add a
firstComment to any post — it's auto-posted ~10 seconds after the main post goes live (up to 3 retries):
{ "posts": [{ "content": "Main post text", "firstComment": "Link: https://postfa.st", "mediaItems": [], "scheduledAt": "...", "socialMediaId": "X_ID" }], "controls": {} }
Supported on: X, Instagram, Facebook, YouTube, Threads. NOT supported on: TikTok, Pinterest, Bluesky, LinkedIn.
See examples/x-first-comment.json.
Schedule a retweet — content and media are ignored:
{ "posts": [{ "content": "", "scheduledAt": "...", "socialMediaId": "X_ID" }], "controls": { "xRetweetUrl": "https://x.com/username/status/1234567890" } }
Schedule multiple posts at different times in a single API call (up to 15 posts per request):
See examples/batch-scheduling.json.
Pass these in the
controls object. See references/platform-controls.md for full details.
| Platform | Key Controls |
|---|---|
| TikTok | , , , , , , , , |
(TIMELINE/STORY/REEL), , , | |
(POST/REEL/STORY), | |
| YouTube | , , , , , , , |
, (for document posts) | |
| X (Twitter) | (retweet), (post to community) |
(required), | |
| Google Business Profile | (required), , , , , , , , , |
| Bluesky | No platform-specific controls — text + images only |
| Threads | No platform-specific controls — text + images/video |
| Telegram | No platform-specific controls — text + images/video/mixed media |
GET /social-media/{id}/pinterest-boards → returns [{ boardId, name }]GET /social-media/{id}/youtube-playlists → returns [{ playlistId, title }]GET /social-media/{id}/gbp-locations → returns [{ id, locationId, title, address, mapsUri }]. Use locationId as gbpLocationId in controlsPOST /social-media/connect-link → returns { connectUrl }. Let clients connect accounts without a PostFast account. Params: expiryDays (1-30, default 7), sendEmail (bool), email (required if sendEmail=true)Global (per API key): 60/min, 150/5min, 300/hour, 2000/day
Per-endpoint:
POST /social-posts: 350/dayGET /social-posts: 200/hourGET /social-posts/analytics: 350/hourPOST /social-media/connect-link: 50/hourPlatform limits:
Check
X-RateLimit-Remaining-* headers. 429 = rate limited, check Retry-After-* header. For batch operations, add a 1-second delay between API calls.
| Platform | Images | Video | Carousel |
|---|---|---|---|
| TikTok | Carousels only | ≤250MB, MP4/MOV, 3s-10min | 2-35 images |
| JPEG/PNG | ≤1GB, 3-90s (Reels) | Up to 10 | |
| ≤30MB, JPG/PNG | 1 per post | Up to 10 images | |
| YouTube | — | Shorts ≤3min, H.264 | — |
| Up to 9 | ≤10min | Up to 9, or documents (PDF/PPTX/DOCX) | |
| X (Twitter) | Up to 4 | — | — |
| 2:3 ratio ideal | Supported | 2-5 images | |
| Google Business Profile | 1 image (JPEG/PNG, 5MB max) | Not supported | — |
| Bluesky | Up to 4 | Not supported | — |
| Threads | Supported | Supported | Up to 10 |
| Telegram | Up to 10 | Supported | Up to 10 mixed media |
socialMediaId is a UUID, not a platform name. Call GET /social-media/my-social-accounts to get valid IDs.key in mediaItems.scheduledAt must be in the future — ISO 8601 UTC format. Past dates return 400.pinterestBoardId — Fetch boards first with GET /social-media/{id}/pinterest-boards.linkedinAttachmentKey — NOT mediaItems. Set mediaItems: [] when posting documents.Content-Type header in your S3 PUT must match what you requested in get-signed-upload-urls.controls object applies to ALL posts in the batch. Platform-irrelevant controls are ignored.firstComment only works on 5 platforms — X, Instagram, Facebook, YouTube, Threads. TikTok, Pinterest, Bluesky, LinkedIn return a validation error.xRetweetUrl is set, the content and mediaItems fields are ignored.page=0 returns the first page. Response pageInfo.page shows 1-based display number.instagramPublishType: "REEL" — Setting instagramTrialReelStrategy without it returns 400. Also cannot be combined with instagramCollaborators.youtubeThumbnailKey only works if the YouTube channel is phone-verified. If it fails, the video still publishes without the custom thumbnail.gbpLocationId — Fetch locations first with GET /social-media/{id}/gbp-locations. Use the locationId field (not id).gbpEventStartDate and gbpEventEndDate are required when gbpTopicType is EVENT or OFFER.coverTimestamp is milliseconds — e.g., "5000" = 5 seconds into the video. Not seconds.coverImageKey platform limits — Instagram Reels: JPEG only, max 8MB. Facebook Reels: any format, max 10MB. Pinterest video: JPEG/PNG. NOT supported on TikTok (use coverTimestamp) or YouTube (use youtubeThumbnailKey).coverTimestamp — Only coverImageKey works for FB Reel covers. coverTimestamp is ignored.This is the most common error. It means the API didn't recognize your key. Check these in order:
Wrong header name. The header must be exactly
pf-api-key. Not Authorization: Bearer, not x-api-key, not api-key. Example:
# Correct curl -H "pf-api-key: YOUR_KEY_HERE" https://api.postfa.st/social-media/my-social-accountsWrong — these all return 403
curl -H "Authorization: Bearer YOUR_KEY_HERE" ... curl -H "x-api-key: YOUR_KEY_HERE" ...
Env var not set. If
$POSTFAST_API_KEY isn't set in your shell, the literal string $POSTFAST_API_KEY gets sent as the key value. Verify it's set:
echo $POSTFAST_API_KEY # Should print a 44-character base64 string ending with "="
If empty, re-export it. If you're using a
.env file, make sure your tool actually loads it (dotenv, direnv, etc.). Shell quoting matters: use double quotes around the value if it contains special characters.
Regenerated key. Each time you click "Generate API Key" in PostFast settings, the previous key is permanently invalidated. Only regenerate if the old key is compromised. If you regenerated and are still using the old key, that's why it fails.
Wrong key entirely. Your PostFast API key is a 44-character base64 string ending with
=. Don't confuse it with keys from other services (OpenAI sk-proj-..., Stripe sk_live_..., etc.).
The
pf-api-key header is either missing from the request or the value is empty. Double-check that your HTTP client is actually sending the header (some tools strip custom headers on redirects).
You've hit the per-workspace rate limit. Check the
Retry-After-Minute, Retry-After-5Minutes, Retry-After-Hour, or Retry-After-Day response header for when you can retry. Limits: 60/min, 150/5min, 300/hour, 2,000/day.
Reference docs:
Ready-to-use examples:
# Auth Header: pf-api-key: $POSTFAST_API_KEYList accounts
GET /social-media/my-social-accounts
Schedule post
POST /social-posts { posts: [{ content, mediaItems, scheduledAt, socialMediaId, firstComment? }], status?, approvalStatus?, controls: {} }
Draft post (no scheduledAt needed)
POST /social-posts { posts: [...], status: "DRAFT", controls: {} }
List posts (page is 0-based, limit max 50)
GET /social-posts?page=0&limit=20 GET /social-posts?page=0&limit=50&platforms=X,LINKEDIN&statuses=SCHEDULED&from=2026-06-01T00:00:00Z&to=2026-06-30T23:59:59Z
Delete post
DELETE /social-posts/:id
Upload media (3 steps)
POST /file/get-signed-upload-urls { contentType, count } PUT <signedUrl> (raw file, matching Content-Type)
then use key in mediaItems
Pinterest boards
GET /social-media/:id/pinterest-boards
YouTube playlists
GET /social-media/:id/youtube-playlists
GBP locations
GET /social-media/:id/gbp-locations
Post analytics (published posts with metrics)
GET /social-posts/analytics?startDate=...&endDate=...&platforms=...
Connect link (for clients)
POST /social-media/connect-link { expiryDays?, sendEmail?, email? }
my-social-accounts first to get valid socialMediaId values.scheduledAt must be ISO 8601 UTC and in the future.pinterestBoardId — fetch boards first.linkedinAttachmentKey instead of mediaItems.mediaItems with sequential sortOrder.coverImageKey in mediaItems for IG Reels, FB Reels, Pinterest video. Use coverTimestamp (milliseconds) for TikTok. YouTube uses youtubeThumbnailKey in controls.firstComment for CTAs and links — keeps the main post clean and gets better engagement.status: "DRAFT" and omit scheduledAt — the user can finalize in the PostFast dashboard.gbpLocationId — fetch locations first with GET /social-media/{id}/gbp-locations.GET /social-posts with from/to filters to check what's already scheduled before adding more.No automatic installation available. Please visit the source repository for installation instructions.
View Installation Instructions1,500+ AI skills, agents & workflows. Install in 30 seconds. Part of the Torly.ai family.
© 2026 Torly.ai. All rights reserved.