Requirements
- Target platform
- OpenClaw
- Install method
- Manual import
- Extraction
- Extract archive
- Prerequisites
- OpenClaw
- Primary doc
- SKILL.md
Publora API — schedule and publish social media posts across 10 platforms (X/Twitter, LinkedIn, Instagram, Threads, TikTok, YouTube, Facebook, Bluesky, Masto...
Publora API — schedule and publish social media posts across 10 platforms (X/Twitter, LinkedIn, Instagram, Threads, TikTok, YouTube, Facebook, Bluesky, Masto...
Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.
I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete.
I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run.
Publora is an affordable REST API for scheduling and publishing social media posts across 11 platforms. Base URL: https://api.publora.com/api/v1
All requests require the x-publora-key header. Keys start with sk_. curl https://api.publora.com/api/v1/platform-connections \ -H "x-publora-key: sk_YOUR_KEY" Get your key: publora.com → Settings → API Keys → Generate API Key. ⚠️ Copy immediately — shown only once.
Always call this first to get valid platform IDs before posting. const res = await fetch('https://api.publora.com/api/v1/platform-connections', { headers: { 'x-publora-key': 'sk_YOUR_KEY' } }); const { connections } = await res.json(); // connections[i].platformId → e.g. "linkedin-ABC123", "twitter-456" // Also returns: tokenStatus, tokenExpiresIn, lastSuccessfulPost, lastError Platform IDs look like: twitter-123, linkedin-ABC, instagram-456, threads-789, etc.
Omit scheduledTime to publish right away: await fetch('https://api.publora.com/api/v1/create-post', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-publora-key': 'sk_YOUR_KEY' }, body: JSON.stringify({ content: 'Your post content here', platforms: ['twitter-123', 'linkedin-ABC'] }) });
Include scheduledTime in ISO 8601 UTC — must be in the future: await fetch('https://api.publora.com/api/v1/create-post', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-publora-key': 'sk_YOUR_KEY' }, body: JSON.stringify({ content: 'Scheduled post content', platforms: ['twitter-123', 'linkedin-ABC'], scheduledTime: '2026-03-16T10:00:00.000Z' }) }); // Response: { postGroupId: "pg_abc123", scheduledTime: "..." }
Omit scheduledTime — post is created as draft. Schedule it later: // Create draft const { postGroupId } = await createPost({ content, platforms }); // Schedule later await fetch(`https://api.publora.com/api/v1/update-post/${postGroupId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'x-publora-key': 'sk_YOUR_KEY' }, body: JSON.stringify({ status: 'scheduled', scheduledTime: '2026-03-16T10:00:00.000Z' }) });
Filter, paginate and sort your scheduled/published posts: // GET /api/v1/list-posts // Query params: status, platform, fromDate, toDate, page, limit, sortBy, sortOrder const res = await fetch( 'https://api.publora.com/api/v1/list-posts?status=scheduled&platform=twitter&page=1&limit=20', { headers: { 'x-publora-key': 'sk_YOUR_KEY' } } ); const { posts, pagination } = await res.json(); // pagination: { page, limit, totalItems, totalPages, hasNextPage, hasPrevPage } Valid statuses: draft, scheduled, published, failed, partially_published
# Get post details GET /api/v1/get-post/:postGroupId # Delete post (also removes media from storage) DELETE /api/v1/delete-post/:postGroupId
Debug failed or partially published posts: const res = await fetch( `https://api.publora.com/api/v1/post-logs/${postGroupId}`, { headers: { 'x-publora-key': 'sk_YOUR_KEY' } } ); const { logs } = await res.json();
Verify a platform connection is healthy before posting: const res = await fetch( 'https://api.publora.com/api/v1/test-connection/linkedin-ABC123', { method: 'POST', headers: { 'x-publora-key': 'sk_YOUR_KEY' } } ); // Returns: { status: "ok"|"error", message, permissions, tokenExpiresIn }
from datetime import datetime, timedelta, timezone import requests HEADERS = { 'Content-Type': 'application/json', 'x-publora-key': 'sk_YOUR_KEY' } base_date = datetime(2026, 3, 16, 10, 0, 0, tzinfo=timezone.utc) posts = ['Monday post', 'Tuesday post', 'Wednesday post', 'Thursday post', 'Friday post'] for i, content in enumerate(posts): scheduled_time = base_date + timedelta(days=i) requests.post('https://api.publora.com/api/v1/create-post', headers=HEADERS, json={ 'content': content, 'platforms': ['twitter-123', 'linkedin-ABC'], 'scheduledTime': scheduled_time.isoformat() })
All media (images and videos) use a 3-step pre-signed upload workflow: Step 1: POST /api/v1/create-post → get postGroupId Step 2: POST /api/v1/get-upload-url → get uploadUrl Step 3: PUT {uploadUrl} with file bytes (no auth needed for S3) import requests HEADERS = { 'Content-Type': 'application/json', 'x-publora-key': 'sk_YOUR_KEY' } # Step 1: Create post post = requests.post('https://api.publora.com/api/v1/create-post', headers=HEADERS, json={ 'content': 'Check this out!', 'platforms': ['instagram-456'], 'scheduledTime': '2026-03-15T14:30:00.000Z' }).json() post_group_id = post['postGroupId'] # Step 2: Get pre-signed upload URL upload = requests.post('https://api.publora.com/api/v1/get-upload-url', headers=HEADERS, json={ 'fileName': 'photo.jpg', 'contentType': 'image/jpeg', 'type': 'image', # or 'video' 'postGroupId': post_group_id }).json() # Step 3: Upload directly to S3 (no auth header needed) with open('./photo.jpg', 'rb') as f: requests.put(upload['uploadUrl'], headers={'Content-Type': 'image/jpeg'}, data=f) For carousels: call get-upload-url N times with the same postGroupId.
X/Twitter and Threads support auto-threading. Separate segments with --- on its own line: await fetch('https://api.publora.com/api/v1/create-post', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-publora-key': 'sk_YOUR_KEY' }, body: JSON.stringify({ content: 'First tweet in the thread.\n\n---\n\nSecond tweet continues.\n\n---\n\nFinal tweet wraps up.', platforms: ['twitter-123', 'threads-789'] }) }); ⚠️ Threads Restriction: Multi-threaded nested posts (content auto-split into connected replies) are temporarily unavailable on Threads due to Threads app reconnection status. Single posts and carousels continue to work normally. Contact support@publora.com for updates.
// Post statistics await fetch('https://api.publora.com/api/v1/linkedin-post-statistics', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-publora-key': 'sk_YOUR_KEY' }, body: JSON.stringify({ postedId: 'urn:li:share:7123456789', platformId: 'linkedin-ABC123', queryTypes: 'ALL' // or: IMPRESSION, MEMBERS_REACHED, RESHARE, REACTION, COMMENT }) }); // Profile summary (followers + aggregated stats) await fetch('https://api.publora.com/api/v1/linkedin-profile-summary', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-publora-key': 'sk_YOUR_KEY' }, body: JSON.stringify({ platformId: 'linkedin-ABC123' }) }); Available analytics endpoints: EndpointDescriptionPOST /linkedin-post-statisticsImpressions, reactions, reshares for a postPOST /linkedin-account-statisticsAggregated account metricsPOST /linkedin-followersFollower count and growthPOST /linkedin-profile-summaryCombined profile overviewPOST /linkedin-create-reactionReact to a postDELETE /linkedin-delete-reactionRemove a reaction
Get real-time notifications when posts are published, fail, or tokens are expiring. // Create a webhook await fetch('https://api.publora.com/api/v1/webhooks', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-publora-key': 'sk_YOUR_KEY' }, body: JSON.stringify({ name: 'My webhook', url: 'https://myapp.com/webhooks/publora', events: ['post.published', 'post.failed', 'token.expiring'] }) }); // Returns: { webhook: { _id, name, url, events, secret, isActive } } // Save the `secret` — it's only shown once. Use it to verify webhook signatures. Valid events: post.scheduled, post.published, post.failed, token.expiring EndpointMethodDescription/webhooksGETList all webhooks/webhooksPOSTCreate webhook/webhooks/:idPATCHUpdate webhook/webhooks/:idDELETEDelete webhook/webhooks/:id/regenerate-secretPOSTRotate webhook secret Max 10 webhooks per account.
Manage multiple users under your workspace account. Contact serge@publora.com to enable Workspace API access. // Create a managed user const user = await fetch('https://api.publora.com/api/v1/workspace/users', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-publora-key': 'sk_CORP_KEY' }, body: JSON.stringify({ email: 'client@example.com', displayName: 'Acme Corp' }) }).then(r => r.json()); // Generate connection URL for user to connect their social accounts const { connectionUrl } = await fetch( `https://api.publora.com/api/v1/workspace/users/${user.id}/connection-url`, { method: 'POST', headers: { 'x-publora-key': 'sk_CORP_KEY' } } ).then(r => r.json()); // Post on behalf of managed user await fetch('https://api.publora.com/api/v1/create-post', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-publora-key': 'sk_CORP_KEY', 'x-publora-user-id': user.id // ← key header for acting on behalf of a user }, body: JSON.stringify({ content: 'Post for Acme Corp!', platforms: ['linkedin-XYZ'] }) }); Workspace endpoints: EndpointMethodDescription/workspace/usersGETList managed users/workspace/usersPOSTCreate managed user/workspace/users/:userIdDELETERemove managed user/workspace/users/:userId/api-keyPOSTGenerate per-user API key/workspace/users/:userId/connection-urlPOSTGenerate OAuth connection link Each managed user has a limit of 100 posts/day (dailyPostsLeft). Never expose your workspace key client-side — use per-user API keys for client-facing scenarios.
⚠️ API limits are often stricter than native app limits. Always design against these. PlatformChar LimitMax ImagesVideo MaxText Only?Twitter/X280 (25K Premium)4 × 5MB2 min / 512MB✅LinkedIn3,00020 × 5MB30 min / 500MB✅Instagram2,20010 × 8MB (JPEG only)90s / 300MB❌Threads50020 × 8MB5 min / 500MB✅TikTok2,200Video only10 min / 4GB❌YouTube5,000 descVideo only12h / 256GB❌Facebook63,20610 × 10MB45 min / 2GB✅Bluesky3004 × 1MB3 min / 100MB✅Mastodon5004 × 16MB~99MB✅Telegram4,096 (1,024 captions)10 × 10MB50MB (Bot API)✅ For full limits detail, see the docs/guides/platform-limits.md in the Publora API Docs.
For platform-specific settings, limits, and examples: publora-linkedin — LinkedIn posts + analytics + reactions publora-twitter — X/Twitter posts & threads publora-instagram — Instagram images/reels/carousels publora-threads — Threads posts publora-tiktok — TikTok videos publora-youtube — YouTube videos publora-facebook — Facebook page posts publora-bluesky — Bluesky posts publora-mastodon — Mastodon posts publora-telegram — Telegram channels
draft — Not scheduled yet scheduled — Waiting to publish published — Successfully posted failed — Publishing failed (check /post-logs) partially_published — Some platforms failed
CodeMeaning400Invalid request (check scheduledTime format, required fields)401Invalid or missing API key403Plan limit reached or Workspace API not enabled404Post/resource not found429Platform rate limit exceeded
Code helpers, APIs, CLIs, browser automation, testing, and developer operations.
Largest current source with strong distribution and engagement signals.