Jump to related tools in the same category or review the original source on GitHub.

Productivity & Tasks @sergebulaev Updated 6/28/2026 1,252 downloads 0 stars Security: Pass

Publora OpenClaw Plugin & Skill | ClawHub

Looking to integrate Publora into your AI workflows? This free OpenClaw plugin from ClawHub helps you automate productivity & tasks tasks instantly, without having to write custom tools from scratch.

What this skill does

Publora API — schedule and publish social media posts across 10 platforms (X/Twitter, LinkedIn, Instagram, Threads, TikTok, YouTube, Facebook, Bluesky, Mastodon, Telegram). Use this skill when the user wants to post, schedule, draft, bulk-schedule, manage workspace users, configure webhooks, or retrieve LinkedIn analytics via Publora.

Install

ClawHub CLI
openclaw skills install @sergebulaev/publora
Node.js (npx)
npx clawhub@latest install publora

Full SKILL.md

Open original
Metadata table.
namedescription
publoraPublora API — schedule and publish social media posts across 10 platforms (X/Twitter, LinkedIn, Instagram, Threads, TikTok, YouTube, Facebook, Bluesky, Mastodon, Telegram). Use this skill when the user wants to post, schedule, draft, bulk-schedule, manage workspace users, configure webhooks, or retrieve LinkedIn analytics via Publora.

SKILL.md content below is scrollable.

Publora API — Core Skill

Publora is an affordable REST API for scheduling and publishing social media posts across 10 platforms (Pinterest is listed internally but not yet supported). Base URL: https://api.publora.com/api/v1

Plans & API Access

Plan Price Posts/Month Platforms
Starter Free 15 LinkedIn & Bluesky
Pro $2.99/account 100/account All
Premium $5.99/account 500/account All

ℹ️ Starter gives API access for LinkedIn and Bluesky. Twitter/X requires Pro or Premium (explicitly excluded from Starter). See publora.com/pricing.

Authentication

All requests require the x-publora-key header. Keys start with sk_ (format: sk_xxxxxxx.xxxxxx...).

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.

Step 0: Get Platform IDs

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.

Post Immediately

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']
  })
});

Schedule a Post

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: "..." }

Save as Draft

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' })
});

List Posts

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 / Delete a Post

# Get post details
GET /api/v1/get-post/:postGroupId

# Delete post (also removes media from storage)
DELETE /api/v1/delete-post/:postGroupId

Get Post Logs

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();

Test a Connection

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 }

Bulk Schedule (a Week of Content)

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()
    })

Media Uploads

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.

Cross-Platform Threading

X/Twitter and Threads support threading. Three methods:

  • Auto-split: Content over the char limit is split automatically at paragraph/sentence/word breaks. Publora adds (1/N) markers (e.g. (1/3)).
  • Manual ---: Use --- on its own line to define exact split points.
  • Explicit [n/m]: Use [1/3], [2/3] markers — Publora preserves them as-is.
// Manual split example
body: JSON.stringify({
  content: 'First tweet.\n\n---\n\nSecond tweet.\n\n---\n\nThird tweet.',
  platforms: ['twitter-123']
})

⚠️ Threads Restriction: Multi-threaded nested posts are temporarily unavailable on Threads (connected replies). Single posts, images, and carousels work normally. Contact [email protected] for updates.

LinkedIn Analytics

// Post statistics — queryTypes is an ARRAY (not a string; 'ALL' is invalid here)
// Use queryType (singular string) for one metric, queryTypes (array) for multiple
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: ['IMPRESSION', 'MEMBERS_REACHED', 'RESHARE', 'REACTION', 'COMMENT']
    // OR: queryType: 'IMPRESSION'  ← singular, returns { count: 123 }
    // Multi-metric response: { metrics: { IMPRESSION: 4521, MEMBERS_REACHED: 3200, ... } }
  })
});

// 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:

Endpoint Description
POST /linkedin-post-statistics Impressions, reactions, reshares for a post
POST /linkedin-account-statistics Aggregated account metrics
POST /linkedin-followers Follower count and growth
POST /linkedin-profile-summary Combined profile overview
POST /linkedin-create-reaction React to a post
DELETE /linkedin-delete-reaction Remove a reaction

Webhooks

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

Endpoint Method Description
/webhooks GET List all webhooks
/webhooks POST Create webhook
/webhooks/:id PATCH Update webhook
/webhooks/:id DELETE Delete webhook
/webhooks/:id/regenerate-secret POST Rotate webhook secret

Max 10 webhooks per account.

Workspace / B2B API

Manage multiple users under your workspace account. Contact [email protected] to enable Workspace API access.

// Create a managed user (returns HTTP 201)
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({ username: '[email protected]', displayName: 'Acme Corp' })
}).then(r => r.json());
// user._id is the MongoDB ObjectId (24-char hex), e.g. "6626a1f5e4b0c91a2d3f4567"

// 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:

Endpoint Method Description
/workspace/users GET List managed users
/workspace/users POST Create managed user
/workspace/users/:userId DELETE Detach managed user (preserves user record, removes workspace association)
/workspace/users/:userId/api-key POST Generate per-user API key
/workspace/users/:userId/connection-url POST Generate OAuth connection link

Each managed user has a dailyPostsLeft field (default: 100) — note this is informational only and not enforced as an actual posting limit. Real limits are workspace-level: monthlyPosts, scheduledPosts, scheduleHorizonDays — enforced at scheduling time. Never expose your workspace key client-side — use per-user API keys for client-facing scenarios.

Platform Limits Quick Reference (API)

⚠️ API limits are often stricter than native app limits. Always design against these.

Platform Char Limit Max Images Video Max Text Only?
Twitter/X 280 (25K Premium) 4 × 5MB 2 min / 512MB
LinkedIn 3,000 10 × 5MB 30 min / 500MB
Instagram 2,200 10 × 8MB (JPEG only) 3 min (180s) Reels / 60s Stories / 300MB
Threads 500 20 × 8MB 5 min / 500MB
TikTok 2,200 Video only 10 min / 4GB
YouTube 5,000 desc Video only 12h / 256GB
Facebook 63,206 10 × 10MB 45 min / 2GB
Bluesky 300 4 × 1MB 3 min / 100MB
Mastodon 500 4 × 16MB ~99MB
Telegram 4,096 (1,024 captions) 10 × 10MB 50MB (Bot API)

For full limits detail, see the docs/guides/platform-limits.md in the Publora API Docs.

Platform-Specific Skills

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

Post Statuses

  • draft — Not scheduled yet
  • scheduled — Waiting to publish
  • published — Successfully posted
  • failed — Publishing failed (check /post-logs)
  • partially_published — Some platforms failed

Errors

Code Meaning
400 Invalid request (check scheduledTime format, required fields)
401 Invalid or missing API key
403 Plan limit reached or Workspace API not enabled
404 Post/resource not found
429 Platform rate limit exceeded
ClawHub Registry URL: https://clawhub.ai/sergebulaev/skills/publora

Related skills

If this matches your use case, these are close alternatives in the same category.