Schedule, publish, draft, and track posts across nine social platforms through one API.
Design & media
postfast
Schedule and manage posts across 11 social platforms through one API.
What it does
Schedule, draft, list, filter, and delete posts through the PostFast API across TikTok, Instagram, Facebook, X, YouTube, LinkedIn, Threads, Bluesky, Pinterest, Telegram, and Google Business Profile. Upload images or MP4 video with signed URLs, cross-post to several connected accounts, and generate client account-connect links. Retrieve supported post analytics and forward-only follower history.
When to use it
- Cross-platform campaign scheduling
- Image and MP4 post uploads
- Scheduled-post review and cleanup
- Post analytics and follower tracking
The skill document
PostFast
Schedule social media posts across 11 platforms from one API. SaaS — no self-hosting needed.
Setup
- Sign up at https://app.postfa.st/register (7-day free trial, no credit card)
- Go to Workspace Settings → generate an API key
- Set the environment variable:
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.
Core Workflow
1. List connected accounts
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, connectionStatus, disabledReason, followerCount?, followerCountUpdatedAt? }. Save the id (the socialMediaId required for every post). followerCount (string) is the latest daily follower/subscriber count, present once PostFast has fetched it.
connectionStatus(always present) —CONNECTED(healthy) orDISABLED(paused; the account needs reconnecting and won't publish until the user does).disabledReason—nullunlessDISABLED, then one ofTOKEN_REVOKED,ACCOUNT_SUSPENDED,PERMISSION_REVOKED,MANUAL.
Check connectionStatus before scheduling — a DISABLED account rejects new scheduled posts (drafts still work).
Platform values: TIKTOK, INSTAGRAM, FACEBOOK, X, YOUTUBE, LINKEDIN, THREADS, BLUESKY, PINTEREST, TELEGRAM, GOOGLE_BUSINESS_PROFILE
2. Schedule a text post (no media)
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"] }.
3. Schedule a post with media (3-step flow)
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/.
4. List scheduled posts
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 inpageInfo.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 onscheduledAt
Example: GET /social-posts?page=0&limit=50&platforms=X,LINKEDIN&statuses=SCHEDULED&from=2026-06-01T00:00:00Z&to=2026-06-30T23:59:59Z
5. Delete a scheduled post
curl -X DELETE -H "pf-api-key: $POSTFAST_API_KEY" https://api.postfa.st/social-posts/POST_ID
6. Cross-post to multiple platforms
Include multiple entries in the posts array, each with a different socialMediaId. They share the same controls and mediaItems keys.
7. Generate a connect link (for clients)
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.
8. Create a draft post
Two unrelated concepts share the word "draft" — don't mix them up:
| What you want | How |
|---|---|
| PostFast draft (any platform) — saved in PostFast, not scheduled, user finalizes from the dashboard | Set status: "DRAFT" and omit scheduledAt. Works for every platform. |
| TikTok app draft — pushes the post to the TikTok app's draft inbox so the user finishes editing on their phone | Set controls.tiktokIsDraft: true. TikTok-only. This is not a PostFast draft state — it still needs scheduledAt. |
⚠️ Common mistake — status placement: status is a top-level field, sibling of posts and controls. It is not a per-post field. If you put it inside the post object, the API silently ignores it, defaults to SCHEDULED, and rejects the request with "All posts must have scheduledAt when status is not present, as default is SCHEDULED".
// ❌ Wrong — status nested inside the post
{ "posts": [{ "content": "...", "status": "DRAFT", "socialMediaId": "..." }] }
// ✅ Right — status at top level
{ "posts": [{ "content": "...", "socialMediaId": "..." }], "status": "DRAFT", "controls": {} }
Per-post fields: content, mediaItems, socialMediaId, scheduledAt (optional for drafts), firstComment.
Top-level fields: status, approvalStatus, controls.
PostFast draft (recommended for "save now, schedule later"):
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": {}
}'
See examples/draft-post.json for the platform-agnostic pattern, or examples/tiktok-app-draft.json for the TikTok-app-inbox case.
9. Get post analytics
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 UUIDs
Returns { "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.
Video watch-time (video posts only): latestMetric also carries avgWatchTimeSeconds, totalWatchTimeSeconds, and videoViews on Facebook, Instagram Reels, YouTube, Pinterest, LinkedIn company pages, and TikTok. TikTok additionally exposes total_time_watched, average_time_watched, and full_video_watched_rate (completion rate) in extras. These are averages and totals, not a per-second retention curve (no platform exposes that). Numbers lag 24-48h, and reach keeps building over the first week or so.
Instagram engagement rates: Instagram posts also carry saveRate on latestMetric (saves ÷ reach as a percentage, rounded to 2 decimals; feed posts, Reels, and carousels). Instagram Reels additionally carry reelsSkipRate (percentage of viewers who skipped the reel in the first 3 seconds, rounded to 2 decimals; may be absent on low-view reels or until metrics refresh).
Supported platforms for analytics: Facebook, Instagram, Threads, LinkedIn, TikTok, YouTube, Pinterest. LinkedIn personal accounts are excluded. YouTube returns views, likes, comments, and total interactions (no reach or shares).
Pinterest specifics (requires a Pinterest business account):
Canonical fields:
impressions,likes(reactions: heart/applaud/idea/etc.),comments— lifetime totals from Pinterest's pin endpoint.shares— 90-day rolling save count. This is the only canonical field that isn't lifetime; Pinterest's v5 API does not expose lifetime save totals.totalInteractions— sum of pin clicks + outbound clicks + saves + reactions + comments. Mostly lifetime, but the saves portion is 90-day, so treat it as a "best available" interaction total rather than strictly lifetime.reach— always null (Pinterest doesn't return it).
extras (lifetime):
pin_clicks— opens of the pin's close-up viewoutbound_clicks— clicks to the destination URLengagement,engagement_rate,pin_click_rate,outbound_click_rate— Pinterest's lifetime analytics fields, passed through when present
extras (90-day rolling):
impressions_90d,pin_clicks_90d,outbound_clicks_90d— same metrics as their lifetime counterparts but for the last 90 dayssave_rate— saves ÷ impressions over the 90-day window
extras for video pins (when present):
video_mrc_views,video_avg_watch_time,video_v50_watch_time,video_10s_views,video_start,quartile_95_percent_view
Refresh cadence: every 6 hours for pins under 14 days old, once a day for pins 14–60 days old. You can also trigger a manual refresh from the dashboard.
Rate limit: 350/hour.
10. Get follower history
Daily follower/subscriber snapshots for a connected account:
curl -s -H "pf-api-key: $POSTFAST_API_KEY" \
"https://api.postfa.st/social-media/ACCOUNT_ID/follower-history?from=2026-05-01T00:00:00.000Z&to=2026-05-31T23:59:59.999Z"
Returns:
{
"socialMediaId": "account-uuid",
"series": [{ "capturedAt": "2026-05-01T00:00:00.000Z", "followerCount": "102" }],
"currentFollowerCount": "106",
"delta": "4",
"trackingStartedAt": "2026-04-20T00:00:00.000Z"
}
from/to(ISO 8601, optional) — default last 90 days, range capped at 365 days.- All counts are strings (bigint).
deltais the net change across the range. - Tracking is forward-only: snapshots begin at
trackingStartedAt(when PostFast started recording the account), with no backfill before then.currentFollowerCount,delta, andtrackingStartedAtmay be absent until the first snapshot lands. - Coverage: Facebook Pages, Instagram, YouTube, Pinterest, Threads, Bluesky, Telegram, LinkedIn company pages, TikTok. Not available: X, personal Facebook.
Common Patterns
Pattern 1: Cross-platform campaign
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.
Pattern 2: Instagram Reel with upload
- Get signed URL with
contentType: "video/mp4" - PUT video to signed URL
- Create post with
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.
Pattern 3: TikTok video with interaction settings
Upload video, then post with interaction controls:
# controls object:
{
"tiktokAllowComments": true,
"tiktokAllowDuet": false,
"tiktokAllowStitch": false,
"tiktokBrandContent": true
}
tiktokPrivacy is deprecated — don't set it. TikTok videos publish at the account's default privacy (no per-post control) and photos default to public. For a private post, save it as a TikTok app draft (tiktokIsDraft: true) and set visibility on the phone.
TikTok Business account: firstComment, follower history, and analytics watch-time all require a TikTok Business account. New connections and reconnects upgrade to Business automatically.
See examples/tiktok-video.json.
Pattern 4: Pinterest pin (board required)
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-boards
# Step 2: Post with board ID
# controls: { "pinterestBoardId": "BOARD_ID", "pinterestLink": "https://yoursite.com" }
See examples/pinterest-pin.json.
Pattern 5: YouTube Short with tags and playlist
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.
Pattern 5b: YouTube video with custom thumbnail
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 URL
# 2. 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.
Pattern 6: Google Business Profile post
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-locations
# Step 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.
Pattern 7: LinkedIn document post
Documents (PDF, PPTX, DOCX) display as swipeable carousels on LinkedIn.
- Get signed URL with
contentType: "application/pdf" - PUT the file to signed URL
- Create post using
linkedinAttachmentKeyinstead ofmediaItems
# controls: { "linkedinAttachmentKey": "file/uuid.pdf", "linkedinAttachmentTitle": "Q1 Marketing Playbook" }
# Note: mediaItems should be [] when using linkedinAttachmentKey
See examples/linkedin-document.json.
Pattern 8: First comment (auto-posted after publish)
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, and TikTok. NOT supported on: Pinterest, Bluesky, LinkedIn. TikTok caveat: Business accounts only, max 150 chars, and comments must be enabled on the post, otherwise the comment is skipped.
See examples/x-first-comment.json.
Pattern 9: X (Twitter) retweet
Schedule a retweet — content and media are ignored:
{
"posts": [{ "content": "", "scheduledAt": "...", "socialMediaId": "X_ID" }],
"controls": { "xRetweetUrl": "https://x.com/username/status/1234567890" }
}
See examples/x-retweet.json.
Pattern 10: Batch scheduling (a week of posts)
Schedule multiple posts at different times in a single API call (up to 15 posts per request):
See examples/batch-scheduling.json.
Pattern 11: Geotag a post with a place (Facebook / Instagram)
Tag a post with a real-world location in two steps: resolve the place, then attach its id in controls.
Step 1. Resolve the place (returns up to 100 address-carrying Facebook Pages, cached 7 days):
curl -sG "https://api.postfa.st/social-media/search-places" \
--data-urlencode "q=national palace of culture" \
-H "pf-api-key: $POSTFAST_API_KEY"
# → [{ "id": "1559011447688271", "name": "...", "link": "https://www.facebook.com/1559011447688271", "city": "Sofia", "country": "Bulgaria", "zip": "1463", "pictureUrl": "https://graph.facebook.com/.../picture?type=small" }]
q needs at least 2 characters. The returned id is a Facebook Page ID that carries location data, and it works on both networks.
Step 2. Attach the id in the controls object (same value either way):
- Facebook feed post:
"facebookPlaceId": "1559011447688271" - Instagram single-media post:
"instagramLocationId": "1559011447688271"
Optionally add facebookPlaceName / instagramLocationName for a readable label. Those are display-only: PostFast stores them for your dashboard and never sends them to Meta.
Where a geotag is allowed:
facebookPlaceId: Facebook feed posts only (text, photo, carousel). Not Reels, Stories, or video.instagramLocationId: a single image, video, reel, or story. Not carousels (2 or more media items).
Limit a Facebook feed post to specific countries (optional, combine with a geotag):
"controls": { "facebookPlaceId": "1559011447688271", "facebookTargetCountries": ["BG", "DE", "AT"] }
facebookTargetCountries takes up to 25 ISO 3166-1 alpha-2 codes and is hard audience gating, not a hint: only logged-in users in those countries can see the post, so total reach drops. Feed posts only.
Batch geotagged posts by platform. controls apply to every post in the posts[] array and each geo field is validated per platform, so send Facebook posts in one request and Instagram posts in another. That keeps a Facebook-only field from landing on an Instagram post and vice versa.
See examples/facebook-geotag.json and examples/instagram-geotag.json.
Platform-Specific Controls
Pass these in the controls object. See references/platform-controls.md for full details.
| Platform | Key Controls |
|---|---|
| TikTok | tiktokTitle (photo carousels, max 90), tiktokAllowComments, tiktokAllowDuet, tiktokAllowStitch, tiktokIsDraft, tiktokIsAigc, tiktokBrandOrganic, tiktokBrandContent, tiktokAutoAddMusic, tiktokMusicSoundId (trending Commercial Music Library sound from the tiktok-sounds helper below — photo/carousel posts on Business-API accounts ONLY; mutually exclusive with tiktokAutoAddMusic, sending both is rejected; not applied when tiktokIsDraft is true), tiktokMusicSoundName (display label for the chosen sound, set it whenever the id is set). tiktokPrivacy is deprecated (no-op) |
instagramPublishType (TIMELINE/STORY/REEL), instagramPostToGrid, instagramCollaborators, instagramTrialReelStrategy, instagramLocationId, instagramLocationName | |
facebookContentType (POST/REEL/STORY), facebookReelsCollaborators, facebookPlaceId, facebookPlaceName, facebookTargetCountries | |
| YouTube | youtubeIsShort, youtubeTitle, youtubePrivacy, youtubePlaylistId, youtubeTags, youtubeMadeForKids, youtubeCategoryId, youtubeThumbnailKey |
linkedinAttachmentKey, linkedinAttachmentTitle (for document posts) | |
| X (Twitter) | xRetweetUrl (retweet) |
pinterestBoardId (required), pinterestLink | |
| Google Business Profile | gbpLocationId (required), gbpTopicType, gbpCallToActionType, gbpCallToActionUrl, gbpEventTitle, gbpEventStartDate, gbpEventEndDate, gbpOfferCouponCode, gbpOfferRedeemUrl, gbpOfferTerms |
| 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 |
Helper Endpoints
- Pinterest boards:
GET /social-media/{id}/pinterest-boards→ returns[{ boardId, name }] - YouTube playlists:
GET /social-media/{id}/youtube-playlists→ returns[{ playlistId, title }] - GBP locations:
GET /social-media/{id}/gbp-locations→ returns[{ id, locationId, title, address, mapsUri }]. UselocationIdasgbpLocationIdin controls - Follower history:
GET /social-media/{id}/follower-history?from=&to=→ daily snapshots{ series: [{ capturedAt, followerCount }], currentFollowerCount, delta, trackingStartedAt }. Forward-only, default 90d, max 365d. Covers every platform except X and personal Facebook - Place search (geotag):
GET /social-media/search-places?q=→ returns[{ id, name, link?, city?, country?, street?, zip?, pictureUrl? }]. Theidis a Facebook Page ID that works as BOTHfacebookPlaceId(Facebook) andinstagramLocationId(Instagram);linkis the place's Facebook Page URL.qneeds min 2 chars, returns up to 100 address-carrying Pages, cached 7 days. Rate limit: 90/hour - Connect link:
POST /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) - TikTok trending sounds:
GET /social-media/{id}/tiktok-sounds?genre=&countryCode=&dateRange=→ up to 100 trending pre-cleared Commercial Music Library tracks[{ musicSoundId, name, artist, duration, thumbnailUrl, previewUrl, rankPosition, genres, ... }](previewUrlplays exactly what gets attached). TikTok Business-API connections only — consumer-connected accounts error withtiktokMusic.requiresBusinessApi(the account must be reconnected).genretakes raw TikTok values likePOP,HIP_HOP/RAP,R&B/SOUL,K-POP(an invalid value 400s with the full valid list in the message);countryCode= 2-letter uppercase, default US (an unknown code returns an empty list);dateRange=1DAY | 7DAY | 30DAY | 90DAY, default 7DAY. The list rotates daily — fetch fresh instead of reusing old ids. Use a result'smusicSoundIdastiktokMusicSoundIdin create_posts controls (plustiktokMusicSoundNamefor the label)
Social Inbox (Comments)
Read and answer the comments on your connected accounts' posts — TikTok (Business connections), Instagram, Facebook Pages, and Threads — through the same API. Comments arrive within seconds of being posted, from the moment an account is connected onward (no history backfill). Comments only: the single DM-shaped action is the official Instagram private reply below. Included on every PostFast plan.
Conversations group comments per post and carry status (OPEN | SNOOZED | CLOSED), unreadCount, assignedToUserId, and a server-computed reply capability: canReply, maxReplyLength, windowState, disabledReason. Always derive whether and how long you can reply from those fields — never from hardcoded platform rules. Each conversation also includes postPreview with the post's caption, thumbnailUrl, and, when available, its public permalink (every field individually optional; use the permalink to link the user straight to the post on the platform — currently null on Instagram). Items are the individual comments and replies, with direction (INBOUND | OUTBOUND), state (VISIBLE | HIDDEN | DELETED), author info, and on Instagram comments canPrivateReply.
- List conversations:
GET /social-inbox/conversations?page=0&limit=20— optional filtersplatforms,socialMediaIds,statuses(comma-separated),unreadOnly,assignedToUserId - One conversation:
GET /social-inbox/conversations/{id} - List items:
GET /social-inbox/conversations/{conversationId}/items?page=0&limit=20&order=ASC(DESCfor newest first) - Unread total:
GET /social-inbox/unread-count - Reply:
POST /social-inbox/items/{itemId}/replywith{ "text": "..." }— respect the conversation'scanReplyandmaxReplyLength - Duplicate guard: repeating the same reply text across a workspace gets the next send rejected with a machine-readable error telling you to vary the wording — treat it as an instruction to rephrase, not a transient failure to retry. Reply sends are also rate-limited per workspace, so batch triage at a human pace
- Instagram private reply:
POST /social-inbox/items/{itemId}/private-replywith{ "text": "..." }— Instagram only, once per comment, within 7 days of the comment, up to ~1,000 bytes (emoji and non-Latin text count multiple); check the item'scanPrivateReplyfirst. Arrives as a DM and may land in the recipient's Message Requests folder - Hide / unhide / delete:
POST /social-inbox/items/{itemId}/statewith{ "action": "HIDE" | "UNHIDE" | "DELETE" }— acts on the platform itself;DELETEis irreversible (and not supported on Threads) - Mark read:
POST /social-inbox/conversations/{conversationId}/read - Set status:
POST /social-inbox/conversations/{conversationId}/statuswith{ "status": "OPEN" | "SNOOZED" | "CLOSED" } - Assign:
POST /social-inbox/conversations/{conversationId}/assignwith{ "assigneeUserId": "..." }
list_accounts marks which accounts feed the inbox via inboxCapable. Facebook accounts connected before 2026-07-28 and Threads accounts connected before 2026-08-04 need one reconnect in the PostFast dashboard before comments flow. Replies you send appear exactly once — no duplicate when the platform reports the reply back. The same operations are exposed as MCP tools (list_inbox_conversations, get_inbox_conversation, list_inbox_items, get_inbox_unread_count, reply_to_inbox_item, send_inbox_private_reply, set_inbox_item_state, mark_inbox_conversation_read, set_inbox_conversation_status, assign_inbox_conversation) in postfast-mcp ≥0.3.0.
Rate Limits
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/hourGET /social-media/search-places: 90/hourPOST /social-media/connect-link: 50/hour
Platform limits:
- X (Twitter) via API: 5 posts per account per day — do not exceed this
- TikTok sounds:
tiktokMusicSoundIdandtiktokAutoAddMusicare mutually exclusive (sending both is rejected), and sound ids rotate daily — fetch a fresh list from the tiktok-sounds helper per session instead of reusing stored ids
Check X-RateLimit-Remaining-* headers. 429 = rate limited, check Retry-After-* header. For batch operations, add a 1-second delay between API calls.
Media Specs Quick Reference
| 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 |
Common Gotchas
- Always fetch accounts first —
socialMediaIdis a UUID, not a platform name. CallGET /social-media/my-social-accountsto get valid IDs. - Media MUST go through 3-step upload — No external URLs. Always: get signed URL → PUT to S3 → use the
keyinmediaItems. scheduledAtmust be in the future — ISO 8601 UTC format. Past dates return 400.- Pinterest ALWAYS requires
pinterestBoardId— Fetch boards first withGET /social-media/{id}/pinterest-boards. - TikTok requires video for standard posts — Images only work in carousels (2-35 images).
- LinkedIn documents use
linkedinAttachmentKey— NOTmediaItems. SetmediaItems: []when posting documents. - Content-Type on S3 PUT must match — The
Content-Typeheader in your S3 PUT must match what you requested inget-signed-upload-urls. - Instagram Reels need video 3-90 seconds — Outside this range returns an error.
- YouTube Shorts need video under 3 minutes — H.264 codec with AAC audio recommended.
- X (Twitter) has a 280 character limit — Longer content is silently truncated.
- Cross-posting shares controls — The
controlsobject applies to ALL posts in the batch. Platform-irrelevant controls are ignored. - X (Twitter) API limit is 5 posts/account/day — Exceeding this risks account restrictions.
firstCommentworks on 6 platforms — X, Instagram, Facebook, YouTube, Threads, and TikTok (TikTok needs a Business account, max 150 chars, comments enabled, otherwise it's skipped). Pinterest, Bluesky, LinkedIn return a validation error.- Retweets ignore content/media — When
xRetweetUrlis set, thecontentandmediaItemsfields are ignored. - LinkedIn documents support PDF, DOC, DOCX, PPT, PPTX — Max 60MB. Cannot mix with regular media.
- Pagination is 0-based —
page=0returns the first page. ResponsepageInfo.pageshows 1-based display number. - Instagram trial reels require
instagramPublishType: "REEL"— SettinginstagramTrialReelStrategywithout it returns 400. Also cannot be combined withinstagramCollaborators. - YouTube custom thumbnails require phone verification —
youtubeThumbnailKeyonly works if the YouTube channel is phone-verified. If it fails, the video still publishes without the custom thumbnail. - GBP ALWAYS requires
gbpLocationId— Fetch locations first withGET /social-media/{id}/gbp-locations. Use thelocationIdfield (notid). - GBP supports only 1 image — No video, no carousels. JPEG/PNG, max 5MB.
- GBP EVENT/OFFER posts require dates —
gbpEventStartDateandgbpEventEndDateare required whengbpTopicTypeisEVENTorOFFER. - GBP content limit is 1,500 characters — Shorter than most platforms.
- GBP posts expire — Standard posts auto-expire after 6 months. Event/Offer posts expire at their end date.
coverTimestampis milliseconds — e.g.,"5000"= 5 seconds into the video. Not seconds.coverImageKeyplatform limits — Instagram Reels: JPEG only, max 8MB. Facebook Reels: any format, max 10MB. Pinterest video: JPEG/PNG. NOT supported on TikTok (usecoverTimestamp) or YouTube (useyoutubeThumbnailKey).- Facebook Reels don't support
coverTimestamp— OnlycoverImageKeyworks for FB Reel covers.coverTimestampis ignored. - Disconnected accounts reject scheduled posts — If an account's
connectionStatusisDISABLED, scheduling to it returns400 socialMediaDisconnected. Drafts still work. CheckconnectionStatusfrommy-social-accountsfirst, and watchlastError.codeforMISSED_DISCONNECTED(post was due while the account was down) orMISSED_NOT_PUBLISHED. - Geotags need a place ID from
search-places— CallGET /social-media/search-places?q=...first; the returnediddoubles asfacebookPlaceId(Facebook feed posts) andinstagramLocationId(Instagram single-media posts). Both take the same numeric ID. - Geotag placement is restricted —
facebookPlaceIdis feed-only (Reels/Stories reject it withfacebookPlaceId.contentType.notSupported, video withfacebookPlaceId.video.notSupported).instagramLocationIdrejects carousels withinstagramLocationId.carousel.notSupported. A geo field on the wrong platform returns*.notSupported. facebookTargetCountriesshrinks reach — Up to 25 ISO 3166-1 alpha-2 codes (over 25 returnsfacebookTargetCountries.tooMany). It's audience gating, not a hint: the post is hidden from anyone outside those countries and from logged-out users. Feed posts only.facebookPlaceName/instagramLocationNameare display-only — PostFast stores them for your dashboard and never sends them to Meta. Only the IDs and country codes reach the platform.- Batch geotagged posts by platform —
controlsapply to the whole batch and geo fields validate per platform, so put Facebook posts in one request and Instagram posts in another.
Troubleshooting
403 "Missing organizationId or activeWorkspaceId"
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. NotAuthorization: Bearer, notx-api-key, notapi-key. Example:# Correct curl -H "pf-api-key: YOUR_KEY_HERE" https://api.postfa.st/social-media/my-social-accounts # Wrong — 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_KEYisn't set in your shell, the literal string$POSTFAST_API_KEYgets 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
.envfile, 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 (OpenAIsk-proj-..., Stripesk_live_..., etc.).
401 Invalid or missing API key
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).
429 Rate limit exceeded
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.
Supporting Resources
Reference docs:
- references/api-reference.md — Complete API endpoint reference with response examples
- references/platform-controls.md — All platform-specific controls with types and defaults
- references/media-specs.md — Media size, format, and dimension limits per platform
- references/upload-flow.md — Detailed media upload walkthrough
Ready-to-use examples:
- examples/EXAMPLES.md — Index of all examples
- examples/cross-platform-post.json — Multi-platform posting
- examples/tiktok-video.json — TikTok with privacy settings
- examples/tiktok-carousel.json — TikTok image carousel
- examples/tiktok-aigc-video.json — TikTok AI-generated video with AIGC label
- examples/draft-post.json — Generic PostFast draft (any platform, no
scheduledAt) - examples/tiktok-app-draft.json — TikTok app draft (
tiktokIsDraftcontrol, pushes to TikTok app inbox) - examples/instagram-reel.json — Instagram Reel
- examples/instagram-reel-cover.json — Instagram Reel with custom cover image
- examples/instagram-story.json — Instagram Story
- examples/instagram-carousel.json — Instagram carousel
- examples/instagram-trial-reel.json — Instagram trial reel (non-followers first)
- examples/facebook-reel.json — Facebook Reel
- examples/facebook-story.json — Facebook Story
- examples/youtube-short.json — YouTube Short with tags
- examples/youtube-video-thumbnail.json — YouTube video with custom thumbnail
- examples/pinterest-pin.json — Pinterest with board
- examples/linkedin-document.json — LinkedIn document post
- examples/x-retweet.json — X scheduled retweet
- examples/x-first-comment.json — X post with auto first comment
- examples/threads-carousel.json — Threads image carousel
- examples/batch-scheduling.json — Week of scheduled posts
- examples/gbp-standard.json — Google Business Profile standard update with CTA
- examples/gbp-event.json — Google Business Profile event post
- examples/gbp-offer.json — Google Business Profile offer with coupon code
- examples/facebook-geotag.json — Facebook feed post geotagged with a place and limited to specific countries
- examples/instagram-geotag.json — Instagram post geotagged with a place
- examples/telegram-mixed-media.json — Telegram mixed media
- examples/pinterest-analytics.json — Pinterest pin analytics w
Questions people ask
- How does API authentication work?
- Set POSTFAST_API_KEY and send it in the `pf-api-key` header to `https://api.postfa.st`. Regenerating the key permanently invalidates the previous one.
- Can it save drafts as well as schedule posts?
- Yes. A PostFast draft uses top-level `status: "DRAFT"` without `scheduledAt`; a TikTok app draft instead uses `controls.tiktokIsDraft: true` and still requires a schedule time.
- Which reporting data can I retrieve?
- Published-post analytics are available for Facebook, Instagram, Threads, LinkedIn, TikTok, YouTube, and Pinterest, with platform-dependent engagement and video metrics. Daily follower history is forward-only and supported for listed account types except X and personal Facebook.
Related skills
Publish and schedule X posts, threads, media, and Markdown-based Articles through a REST API.
Manage Instagram profiles, posts, media publishing, and comments through the Instagram Graph API.
Plans and generates platform-adapted social visuals with safe-area checks, layered copy, and approval steps.
Access LinkedIn profiles, posts, media uploads, public libraries, and advertising APIs through managed OAuth.
Create Reels, TikToks, carousels, Stories, and posts from prompts with automated production.