Posta is a social media management platform that lets you create, schedule, and publish posts across Instagram, TikTok, Facebook, X/Twitter, LinkedIn, YouTube, Pinterest, Threads, and Bluesky.
This skill enables you to interact with the Posta API to manage social media content end-to-end: authenticate, list accounts, upload media, create/schedule/publish posts, generate AI content, and view analytics.
POSTA_API_TOKEN — Recommended. Personal API token (starts with posta_). Long-lived, revocable, no password exposure.POSTA_EMAIL + POSTA_PASSWORD — Legacy login. The skill logs in and caches a JWT automatically.If POSTA_API_TOKEN is set, email/password are not needed and the login flow is skipped entirely.
POSTA_BASE_URL — API base URL (default: https://api.getposta.app/v1)FIREWORKS_API_KEY — Fireworks.ai API key (for image generation). Keys start with fw_. Get one at https://fireworks.ai/account/api-keys. The skill auto-discovers this from env vars, ~/.posta/credentials, or .env files.GEMINI_API_KEY — Google Gemini API key (for caption/text generation)OPENAI_API_KEY — OpenAI API key (alternative text generation)The skill searches a fixed list of dedicated config files for POSTA_API_TOKEN (or legacy POSTA_EMAIL/POSTA_PASSWORD). Only exact variable names are matched — no other file content is read. Shell profiles (~/.zshrc, ~/.bashrc) are never accessed. Search order:
~/.posta/credentials — dedicated Posta config file (preferred).env, .env.local, .env.production in the working directoryIf POSTA_API_TOKEN is found, the skill uses it immediately and skips email/password lookup. See SECURITY.md in the repo root for full details.
Source the bash helper for all API interactions:
source "${POSTA_SKILL_ROOT:-${OPENCLAW_SKILL_ROOT:-${CLAUDE_PLUGIN_ROOT:-}}}/skills/posta/scripts/posta-api.sh"
This provides:
posta_login, posta_get_token, posta_api, posta_discover_credentialsposta_detect_mime, posta_upload_media, posta_upload_from_url, posta_list_media, posta_get_media, posta_delete_media, posta_generate_carousel_pdfposta_list_posts, posta_create_post, posta_create_post_from_file, posta_get_post, posta_update_post, posta_delete_post, posta_schedule_post, posta_publish_post, posta_cancel_post, posta_get_calendarposta_list_platforms, posta_get_platform_specs, posta_get_aspect_ratios, posta_get_platform, posta_get_pinterest_boardsposta_get_analytics_overview, posta_get_analytics_capabilities, posta_get_analytics_posts, posta_get_post_analytics, posta_get_analytics_trends, posta_get_best_times, posta_get_content_types, posta_get_hashtag_analytics, posta_compare_posts, posta_get_benchmarks, posta_export_analytics_csv, posta_export_analytics_pdf, posta_refresh_post_analytics, posta_refresh_all_analyticsposta_get_plan, posta_get_profile, posta_update_profilefireworks_validate_keyAuthentication is automatic. If POSTA_API_TOKEN is set, the skill uses it directly — no login step needed. Otherwise it falls back to email/password login with JWT caching. If a request returns 401:
source "${POSTA_SKILL_ROOT:-${OPENCLAW_SKILL_ROOT:-${CLAUDE_PLUGIN_ROOT:-}}}/skills/posta/scripts/posta-api.sh"
# Token is fetched/cached automatically on first API call
To verify credentials are working:
posta_api GET "/auth/me"
ACCOUNTS=$(posta_list_accounts)
# Returns a plain array (wrapper is auto-unwrapped)
echo "$ACCOUNTS" | jq -r '.[] | "\(.platform)\t\(.username)\t\(.isActive)"'
Display as a table showing: Platform, Username, Active status, Last used.
> Note: Account IDs from posta_list_accounts are integers (e.g. 35). Wrap them in quotes when passing to socialAccountIds: "socialAccountIds": ["35"]
The upload flow has 3 steps: create signed URL → PUT binary → confirm upload. MIME type is auto-detected from the file — no need to specify it manually.
From a local file (auto-detect MIME):
MEDIA_ID=$(posta_upload_media "/path/to/file.jpg")
From a local file (explicit MIME):
MEDIA_ID=$(posta_upload_media "/path/to/file.jpg" "image/jpeg")
From a URL (auto-detect from extension):
MEDIA_ID=$(posta_upload_from_url "https://example.com/image.png")
Detect MIME type separately:
MIME=$(posta_detect_mime "/path/to/file.mp4")
# Returns: video/mp4
Supported formats:
image/jpeg, image/png, image/webp, image/gif (max 20MB)video/mp4, video/quicktime, video/webm (max 500MB)After upload, the media enters processing status. For images this is fast (thumbnails/variants). For videos it takes longer. Check status with:
posta_get_media "$MEDIA_ID"
List media library:
ALL_MEDIA=$(posta_list_media)
IMAGES_ONLY=$(posta_list_media "image")
COMPLETED=$(posta_list_media "" "completed" 50)
Delete media:
posta_delete_media "$MEDIA_ID"
Generate carousel PDF from images:
RESULT=$(posta_generate_carousel_pdf '["media-id-1", "media-id-2", "media-id-3"]' "My Carousel Title")
Create a draft post:
POST=$(posta_create_post '{
"caption": "Your caption here",
"hashtags": ["tag1", "tag2"],
"mediaIds": ["media-uuid"],
"socialAccountIds": ["35", "42"],
"isDraft": true
}')
POST_ID=$(echo "$POST" | jq -r '.id')
Create a post with multiline caption (from file):
cat > /tmp/caption.txt << 'EOF'
Line one of the caption.
Line two with details.
Call to action here.
EOF
POST=$(posta_create_post_from_file /tmp/caption.txt '["media-uuid"]' '["35", "42"]' true '["tag1", "tag2"]')
POST_ID=$(echo "$POST" | jq -r '.id')
Schedule for a specific time:
posta_schedule_post "$POST_ID" "2026-03-15T09:00:00Z"
Reschedule an already-scheduled post:
The API only allows scheduling posts in draft status. To reschedule, cancel first, then schedule again:
posta_cancel_post "$POST_ID"
posta_schedule_post "$POST_ID" "2026-03-16T09:00:00Z"
Publish immediately:
posta_publish_post "$POST_ID"
Platform-specific configuration (optional):
{
"platformConfigurations": {
"tiktok": {
"privacyLevel": "PUBLIC_TO_EVERYONE",
"allowComment": true,
"allowDuet": false,
"allowStitch": false
},
"pinterest": {
"boardId": "board-id",
"link": "https://your-link.com",
"altText": "Image description"
}
}
}
Note: Either caption or at least one mediaIds entry is required. Text-only posts work for X/Twitter.
Generate an image with Fireworks SDXL:
curl -s -X POST \
"https://api.fireworks.ai/inference/v1/image_generation/accounts/fireworks/models/stable-diffusion-xl-1024-v1-0" \
-H "Authorization: Bearer ${FIREWORKS_API_KEY}" \
-H "Content-Type: application/json" \
-H "Accept: image/png" \
-d '{
"prompt": "your descriptive prompt, photorealistic, natural colors, high quality, detailed",
"negative_prompt": "text, watermark, blurry, low quality, distorted",
"width": 1024, "height": 1024, "steps": 30, "guidance_scale": 7.5
}' --output /tmp/generated.png
MEDIA_ID=$(posta_upload_media /tmp/generated.png "image/png")
Generate a caption with Gemini:
CAPTION=$(curl -s -X POST \
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"contents": [{"parts": [{"text": "Write an engaging Instagram caption about [topic]. Max 150 words."}]}],
"generationConfig": {"temperature": 0.8, "maxOutputTokens": 300}
}' | jq -r '.candidates[0].content.parts[0].text')
See content-generation.md for full patterns including OpenAI and hashtag generation.
Overview stats:
OVERVIEW=$(posta_get_analytics_overview "30d")
echo "$OVERVIEW" | jq '{totalPosts, totalImpressions, totalEngagements, avgEngagementRate}'
Best posting times:
BEST_TIMES=$(posta_get_best_times)
Top performing posts:
TOP=$(posta_api GET "/analytics/posts?limit=10&sortBy=engagements&sortOrder=desc")
Trends over time:
TRENDS=$(posta_api GET "/analytics/trends?period=30d&metric=engagements")
Check plan and usage:
PLAN=$(posta_get_plan)
echo "$PLAN" | jq '{plan, usage, limits}'
Query platform capabilities, character limits, media requirements, and supported features before creating posts.
List all supported platforms:
posta_list_platforms
Get full specs (char limits, media requirements, features):
SPECS=$(posta_get_platform_specs)
Get specs for a specific platform:
posta_get_platform "instagram"
Get aspect ratio reference:
posta_get_aspect_ratios
Get Pinterest boards for a connected account:
BOARDS=$(posta_get_pinterest_boards "$ACCOUNT_ID")
Use platform discovery to validate content before posting — check character limits, required media dimensions, and supported post types.
View scheduled and posted content on a calendar:
CALENDAR=$(posta_get_calendar "2026-03-01" "2026-03-31")
echo "$CALENDAR" | jq '.items[] | {id, caption: .caption[:50], status, scheduledAt}'
Analytics capabilities (what your plan supports):
posta_get_analytics_capabilities
Top performing posts (sorted, paginated):
TOP=$(posta_get_analytics_posts 10 0 "engagements" "desc")
Single post analytics:
posta_get_post_analytics "$POST_ID"
Trends over time with custom period:
TRENDS=$(posta_get_analytics_trends "90d" "engagement_rate")
Content type performance breakdown:
posta_get_content_types
Hashtag performance (pro plan):
posta_get_hashtag_analytics
Compare posts side by side (2-4 posts, pro plan):
posta_compare_posts "post-id-1,post-id-2,post-id-3"
Engagement benchmarks (pro plan):
posta_get_benchmarks
Export analytics:
posta_export_analytics_csv "30d"
posta_export_analytics_pdf "90d"
Refresh analytics:
posta_refresh_post_analytics "$POST_RESULT_ID"
posta_refresh_all_analytics # Rate limited: 1 per hour
posta_get_plan before attempting operations that may exceed limits (posts, accounts, storage).isDraft: true when creating posts, then schedule or publish after user confirmation.posta_create_post_from_file for multiline captions. Write the caption to a temp file and use the file-based helper instead of trying to embed multiline text in JSON strings. This avoids escaping issues./tmp/.posta_last_response for captured output. When capturing posta_api output in a variable with $(), avoid using echo to re-output it — macOS echo corrupts \n in JSON strings. Instead pipe directly (posta_api ... | jq) or read from the file (jq ... /tmp/.posta_last_response).posta_get_platform_specs or posta_get_platform "" to check character limits, required media dimensions, and supported features. This prevents failed posts due to platform constraints.posta_upload_media and posta_upload_from_url auto-detect it from the file content or extension. Only specify MIME type manually when the auto-detection might be wrong (e.g., .bin files).platformConfigurations.tiktok.privacyLevel — TikTok requires it and publishing will fail without it. Use "PUBLIC_TO_EVERYONE" unless the user specifies otherwise. Valid values: PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, SELF_ONLY, FOLLOWER_OF_CREATOR.共 2 个版本