Rate Limits
This guide covers platform-specific rate limits, PostPost API rate limits, and strategies for scheduling posts at optimal engagement times.
Quick Start: High-Volume Multi-Platform Scheduling
For scheduling hundreds of posts across all 10 platforms simultaneously, use this production-ready approach:
Installation
# JavaScript/Node.js
npm install axios
# Python
pip install requestsEnvironment Setup
# Set your API key (get it from postpost.dev → API in sidebar)
export PUBLORA_API_KEY="sk_your_api_key_here"Minimal Working Example
JavaScript (Node.js):
// high-volume-scheduler.js
// Run: PUBLORA_API_KEY=sk_xxx node high-volume-scheduler.js
const axios = require('axios');
const API_KEY = process.env.PUBLORA_API_KEY;
const BASE_URL = 'https://api.postpost.dev/api/v1';
// Advisory/recommended limits — NOT enforced by PostPost.
// These are conservative guidelines to avoid platform-side rate limiting.
const PLATFORM_LIMITS = {
twitter: { perHour: 10, perDay: 50 }, // advisory
linkedin: { perHour: 5, perDay: 20 }, // advisory
instagram: { perHour: 3, perDay: 10 }, // advisory
threads: { perHour: 10, perDay: 50 }, // advisory
tiktok: { perHour: 2, perDay: 10 }, // advisory
facebook: { perHour: 5, perDay: 25 }, // advisory
youtube: { perHour: 2, perDay: 10 }, // advisory
bluesky: { perHour: 10, perDay: 100 }, // advisory
mastodon: { perHour: 5, perDay: 50 }, // advisory
telegram: { perHour: 20, perDay: 300 }, // advisory
};
async function schedulePost(content, platforms, scheduledTime) {
const response = await axios.post(`${BASE_URL}/create-post`, {
content,
platforms,
scheduledTime
}, {
headers: {
'x-api-key': API_KEY,
'Content-Type': 'application/json'
}
});
return response.data;
}
async function main() {
// Your platform connection IDs (from /platform-connections endpoint)
const platforms = ['twitter-123456', 'linkedin-ABC123', 'threads-789012'];
// Schedule 10 posts, 1 hour apart
const now = new Date();
for (let i = 0; i < 10; i++) {
const scheduledTime = new Date(now.getTime() + (i + 1) * 3600000);
const result = await schedulePost(
`Post ${i + 1}: Your content here`,
platforms,
scheduledTime.toISOString()
);
console.log(`Scheduled post ${i + 1}: ${result.postGroupId}`);
}
}
main().catch(console.error);Python:
#!/usr/bin/env python3
# high_volume_scheduler.py
# Run: PUBLORA_API_KEY=sk_xxx python high_volume_scheduler.py
import os
import requests
from datetime import datetime, timedelta, timezone
API_KEY = os.environ['PUBLORA_API_KEY']
BASE_URL = 'https://api.postpost.dev/api/v1'
# Advisory/recommended limits — NOT enforced by PostPost.
# These are conservative guidelines to avoid platform-side rate limiting.
PLATFORM_LIMITS = {
'twitter': {'per_hour': 10, 'per_day': 50}, # advisory
'linkedin': {'per_hour': 5, 'per_day': 20}, # advisory
'instagram': {'per_hour': 3, 'per_day': 10}, # advisory
'threads': {'per_hour': 10, 'per_day': 50}, # advisory
'tiktok': {'per_hour': 2, 'per_day': 10}, # advisory
'facebook': {'per_hour': 5, 'per_day': 25}, # advisory
'youtube': {'per_hour': 2, 'per_day': 10}, # advisory
'bluesky': {'per_hour': 10, 'per_day': 100}, # advisory
'mastodon': {'per_hour': 5, 'per_day': 50}, # advisory
'telegram': {'per_hour': 20, 'per_day': 300}, # advisory
}
def schedule_post(content: str, platforms: list, scheduled_time: str) -> dict:
response = requests.post(
f'{BASE_URL}/create-post',
json={
'content': content,
'platforms': platforms,
'scheduledTime': scheduled_time
},
headers={
'x-api-key': API_KEY,
'Content-Type': 'application/json'
}
)
response.raise_for_status()
return response.json()
def main():
# Your platform connection IDs (from /platform-connections endpoint)
platforms = ['twitter-123456', 'linkedin-ABC123', 'threads-789012']
# Schedule 10 posts, 1 hour apart
now = datetime.now(timezone.utc)
for i in range(10):
scheduled_time = now + timedelta(hours=i + 1)
result = schedule_post(
f'Post {i + 1}: Your content here',
platforms,
scheduled_time.isoformat()
)
print(f"Scheduled post {i + 1}: {result['postGroupId']}")
if __name__ == '__main__':
main()PostPost Plan Limits
Each PostPost plan enforces limits on monthly posts, scheduled (pending) posts, and how far in advance you can schedule.
| Plan | Monthly Posts | Connections | Post Limit Scope | Scheduled Posts | Schedule Horizon | API/MCP Access | Platforms |
|---|---|---|---|---|---|---|---|
| Starter (Free) | 15 | 3 | Account-wide | 3 pending | 7 days | No | All except Twitter |
| Pro Monthly ($5.99/mo) | 100 | Per seat* | Per connection | 100 pending | Unlimited | Yes | All |
| Pro Yearly ($2.99/mo) | 100 | Per seat* | Per connection | 100 pending | Unlimited | Yes | All |
| Premium Monthly ($9.99/mo) | 500 | Per seat* | Per connection | 500 pending | Unlimited | Yes | All |
| Premium Yearly ($5.99/mo) | 500 | Per seat* | Per connection | 500 pending | Unlimited | Yes | All |
*Paid plan connection limits are based on subscription quantity (seat count), not truly unlimited. Each seat in your subscription adds to your total allowed connections.
Key details:
- Starter plan limits are account-wide (15 posts total across all connections, max 3 connections,
scheduledPosts: 3,scheduleHorizonDays: 7). Paid plans count limits per connection (e.g., 100 posts per LinkedIn connection + 100 posts per Twitter connection). - Starter plan does not include API or MCP access (
apiAccess: false,mcpAccess: false). You must upgrade to Pro or Premium to use the REST API or MCP server. Starter is dashboard-only. - Starter plan excludes Twitter/X. To post to Twitter, upgrade to a paid plan.
Note: Per-request API rate limiting (requests/minute) is planned but not currently enforced. The limits above (monthly posts, scheduled posts, schedule horizon) are the enforced plan-based limits. Future API rate limiting will be documented here when implemented.
Platform-Specific Rate Limits (Advisory)
Each social media platform has its own posting limits. The table below shows advisory/recommended limits based on platform documentation and best practices. These limits are not enforced by PostPost -- they are guidelines to help you avoid being rate-limited or penalized by the platforms themselves. PostPost handles automatic queuing and retry when platform limits are hit.
All 10 Supported Platforms
| Platform | Posts/Day (platform limit) | Posts/Hour (platform limit) | PostPost Recommended | Notes |
|---|---|---|---|---|
| X/Twitter | 2,400 tweets | 300 tweets | 50/day, 10/hour | Threading counts as multiple posts |
| 100 posts | 25 posts | 20/day, 5/hour | Personal + organization pages | |
| 25 posts | 10 posts | 10/day, 3/hour | Feed posts only; Stories separate | |
| Threads | 250 posts | 50 posts | 50/day, 10/hour | Threading supported |
| TikTok | 50 videos | 10 videos | 10/day, 2/hour | Video uploads only |
| YouTube | 50 videos | 10 videos | 10/day, 2/hour | Per channel |
| 50 posts | 25 posts | 25/day, 5/hour | Per page | |
| Bluesky | 1,666 posts | 100 posts | 100/day, 10/hour | Per account |
| Mastodon | 300 posts | 30 posts | 50/day, 5/hour | Instance-dependent |
| Telegram | Unlimited | 20 msg/sec | 300/day, 20/hour | Channel/group dependent |
"PostPost Recommended" limits are conservative defaults built into the scheduler examples below to ensure reliable publishing without hitting platform limits.
How PostPost Handles Platform Limits
- Automatic Queuing - If a platform rate limit is hit, PostPost queues the post and retries automatically
- Smart Distribution - When scheduling many posts, PostPost distributes them to avoid hitting limits
- Error Reporting - If a post fails due to platform limits, status shows
failedwith the reason
API Reference for Scheduling
The scheduler examples use these PostPost API endpoints:
Create Post
POST https://api.postpost.dev/api/v1/create-post
Content-Type: application/json
x-api-key: sk_your_api_key
{
"content": "Your post content",
"platforms": ["twitter-123456", "linkedin-ABC123"],
"scheduledTime": "2026-03-15T14:00:00.000Z"
}Response:
{
"success": true,
"postGroupId": "67a1b2c3d4e5f6a7b8c9d0e1"
}Get Post Status
GET https://api.postpost.dev/api/v1/get-post/{postGroupId}
x-api-key: sk_your_api_keyResponse:
{
"postGroupId": "67a1b2c3d4e5f6a7b8c9d0e1",
"status": "published",
"content": "Your post content",
"posts": [
{
"postId": "p_1",
"platform": "twitter",
"platformId": "123456",
"status": "published",
"publishedUrl": "https://twitter.com/user/status/123456"
},
{
"postId": "p_2",
"platform": "linkedin",
"platformId": "ABC123",
"status": "published",
"publishedUrl": "https://linkedin.com/posts/..."
}
]
}Note: The
get-postendpoint returns the rawplatformId(e.g.,"123456"), not the composite format (e.g.,"twitter-123456"). The compositeplatform-platformIdformat is only returned by thelist-postsandplatform-connectionsendpoints.
Get Platform Connections
GET https://api.postpost.dev/api/v1/platform-connections
x-api-key: sk_your_api_keyResponse:
{
"success": true,
"connections": [
{ "platformId": "twitter-123456789", "username": "myaccount", "displayName": "My Account", "tokenStatus": "unknown" },
{ "platformId": "linkedin-ABC123", "username": "My Company", "displayName": "My Company", "tokenStatus": "valid" },
{ "platformId": "threads-789012", "username": "mythreads", "displayName": "My Threads", "tokenStatus": "valid" }
]
}Use the platformId field from connections as the platform identifier when creating posts.
Examples
JavaScript - PostPost API Client
class PostPostClient {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://api.postpost.dev/api/v1';
}
async request(endpoint, options = {}) {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
...options,
headers: {
'x-api-key': this.apiKey,
'Content-Type': 'application/json',
...options.headers
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || `HTTP ${response.status}`);
}
return response.json();
}
async createPost(content, platforms, scheduledTime) {
return this.request('/create-post', {
method: 'POST',
body: JSON.stringify({ content, platforms, scheduledTime })
});
}
async listPosts(params = {}) {
const query = new URLSearchParams(params).toString();
return this.request(`/list-posts?${query}`);
}
}
// Usage
const client = new PostPostClient('YOUR_API_KEY');
for (let i = 0; i < 100; i++) {
await client.createPost(
`Post ${i + 1}`,
['twitter-123'],
new Date(Date.now() + i * 3600000).toISOString()
);
}Python - PostPost API Client with Retry
import requests
import time
from typing import Dict, Any, Optional
class PostPostClient:
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = 'https://api.postpost.dev/api/v1'
def _request(
self,
method: str,
endpoint: str,
data: Optional[Dict] = None,
params: Optional[Dict] = None,
max_retries: int = 3
) -> Dict[str, Any]:
"""Make a request with retry on failure."""
headers = {
'x-api-key': self.api_key,
'Content-Type': 'application/json'
}
for attempt in range(max_retries):
response = requests.request(
method,
f'{self.base_url}{endpoint}',
headers=headers,
json=data,
params=params
)
if response.status_code >= 500:
backoff = 2 ** attempt
print(f"Server error. Retrying in {backoff}s (attempt {attempt + 1})")
time.sleep(backoff)
continue
response.raise_for_status()
return response.json()
raise Exception("Max retries exceeded")
def create_post(
self,
content: str,
platforms: list,
scheduled_time: Optional[str] = None
) -> Dict[str, Any]:
data = {'content': content, 'platforms': platforms}
if scheduled_time:
data['scheduledTime'] = scheduled_time
return self._request('POST', '/create-post', data=data)
def list_posts(self, **params) -> Dict[str, Any]:
return self._request('GET', '/list-posts', params=params)
# Usage
client = PostPostClient('YOUR_API_KEY')
# Bulk create
from datetime import datetime, timedelta, timezone
base_time = datetime.now(timezone.utc) + timedelta(hours=1)
for i in range(100):
scheduled = (base_time + timedelta(hours=i)).isoformat()
result = client.create_post(
f"Scheduled post {i + 1}",
['twitter-123', 'linkedin-ABC'],
scheduled
)
print(f"Created post {i + 1}: {result['postGroupId']}")Peak Engagement Times
Optimal posting times vary by platform and audience. Use these guidelines as a starting point, then analyze your own analytics.
Platform-Specific Best Times (UTC)
| Platform | Best Days | Best Hours (UTC) | Peak Engagement |
|---|---|---|---|
| X/Twitter | Tue-Thu | 13:00-16:00 | Wednesday 12:00 |
| Tue-Thu | 10:00-12:00 | Tuesday 10:00 | |
| Mon, Wed | 11:00-14:00, 19:00-21:00 | Wednesday 11:00 | |
| Threads | Tue-Thu | 12:00-15:00 | Wednesday 13:00 |
| TikTok | Tue-Thu | 15:00-21:00 | Thursday 19:00 |
| YouTube | Thu-Sat | 14:00-18:00 | Friday 15:00 |
| Wed-Fri | 13:00-16:00 | Wednesday 13:00 | |
| Bluesky | Mon-Fri | 14:00-17:00 | Tuesday 15:00 |
JavaScript - Optimal Time Scheduler
const OPTIMAL_TIMES = {
twitter: {
bestDays: [2, 3, 4], // Tue, Wed, Thu (0 = Sunday)
bestHours: [13, 14, 15, 16],
peakDay: 3,
peakHour: 12
},
linkedin: {
bestDays: [2, 3, 4],
bestHours: [10, 11, 12],
peakDay: 2,
peakHour: 10
},
instagram: {
bestDays: [1, 3], // Mon, Wed
bestHours: [11, 12, 13, 14, 19, 20, 21],
peakDay: 3,
peakHour: 11
},
threads: {
bestDays: [2, 3, 4],
bestHours: [12, 13, 14, 15],
peakDay: 3,
peakHour: 13
},
tiktok: {
bestDays: [2, 3, 4],
bestHours: [15, 16, 17, 18, 19, 20, 21],
peakDay: 4,
peakHour: 19
},
facebook: {
bestDays: [3, 4, 5], // Wed, Thu, Fri
bestHours: [13, 14, 15, 16],
peakDay: 3,
peakHour: 13
}
};
function getNextOptimalTime(platform, fromDate = new Date()) {
const config = OPTIMAL_TIMES[platform];
if (!config) throw new Error(`Unknown platform: ${platform}`);
const result = new Date(fromDate);
result.setUTCMinutes(0, 0, 0);
// Find next best day
let daysToAdd = 0;
while (!config.bestDays.includes(result.getUTCDay()) || daysToAdd === 0) {
result.setUTCDate(result.getUTCDate() + 1);
daysToAdd++;
if (daysToAdd > 7) break;
}
// Set to best hour
result.setUTCHours(config.bestHours[0]);
return result;
}
function getPeakTime(platform, weekStart = new Date()) {
const config = OPTIMAL_TIMES[platform];
if (!config) throw new Error(`Unknown platform: ${platform}`);
const result = new Date(weekStart);
result.setUTCHours(0, 0, 0, 0);
// Move to peak day
const currentDay = result.getUTCDay();
const daysUntilPeak = (config.peakDay - currentDay + 7) % 7;
result.setUTCDate(result.getUTCDate() + daysUntilPeak);
// Set peak hour
result.setUTCHours(config.peakHour);
return result;
}
function distributePostsOptimally(platforms, count, startDate = new Date()) {
const schedule = [];
const date = new Date(startDate);
for (let i = 0; i < count; i++) {
const platform = platforms[i % platforms.length];
const optimalTime = getNextOptimalTime(platform, date);
schedule.push({
index: i,
platform,
scheduledTime: optimalTime.toISOString()
});
// Move forward for next post
date.setTime(optimalTime.getTime() + 3600000); // +1 hour minimum gap
}
return schedule;
}
// Usage
const schedule = distributePostsOptimally(
['twitter', 'linkedin', 'instagram'],
9, // 3 posts per platform
new Date()
);
console.log('Optimal posting schedule:');
schedule.forEach(item => {
console.log(`${item.platform}: ${item.scheduledTime}`);
});Python - Queue-Based Scheduler with Rate Limits
from datetime import datetime, timedelta, timezone
from typing import List, Dict, Optional
from dataclasses import dataclass
from collections import defaultdict
import heapq
@dataclass
class PlatformConfig:
posts_per_hour: int
posts_per_day: int
best_hours: List[int] # UTC hours
best_days: List[int] # 0=Monday, 6=Sunday
PLATFORM_LIMITS = {
'twitter': PlatformConfig(
posts_per_hour=10,
posts_per_day=50,
best_hours=[13, 14, 15, 16],
best_days=[1, 2, 3] # Tue, Wed, Thu
),
'linkedin': PlatformConfig(
posts_per_hour=5,
posts_per_day=20,
best_hours=[10, 11, 12],
best_days=[1, 2, 3]
),
'instagram': PlatformConfig(
posts_per_hour=3,
posts_per_day=10,
best_hours=[11, 12, 13, 14, 19, 20, 21],
best_days=[0, 2] # Mon, Wed
),
'tiktok': PlatformConfig(
posts_per_hour=2,
posts_per_day=10,
best_hours=[15, 16, 17, 18, 19, 20, 21],
best_days=[1, 2, 3]
),
}
class OptimalScheduler:
"""
Schedule posts optimally across platforms considering:
- Platform-specific rate limits
- Peak engagement times
- Even distribution across time slots
"""
def __init__(self):
self.platform_queues: Dict[str, List] = defaultdict(list)
self.posts_per_hour: Dict[str, Dict[str, int]] = defaultdict(lambda: defaultdict(int))
self.posts_per_day: Dict[str, Dict[str, int]] = defaultdict(lambda: defaultdict(int))
def get_next_optimal_slot(
self,
platform: str,
after: datetime
) -> datetime:
"""Find the next available optimal time slot for a platform."""
config = PLATFORM_LIMITS.get(platform)
if not config:
# Unknown platform - just space by 1 hour
return after + timedelta(hours=1)
candidate = after.replace(minute=0, second=0, microsecond=0)
for _ in range(168): # Check up to 1 week ahead
candidate += timedelta(hours=1)
day_key = candidate.strftime('%Y-%m-%d')
hour_key = candidate.strftime('%Y-%m-%d-%H')
# Check rate limits
if self.posts_per_day[platform][day_key] >= config.posts_per_day:
# Skip to next day
candidate = candidate.replace(hour=0) + timedelta(days=1)
continue
if self.posts_per_hour[platform][hour_key] >= config.posts_per_hour:
continue
# Check if this is an optimal time
if (candidate.weekday() in config.best_days and
candidate.hour in config.best_hours):
return candidate
# If no optimal slot found, return next available
return after + timedelta(hours=1)
def schedule_post(
self,
platform: str,
after: datetime = None
) -> datetime:
"""Schedule a single post and return its time."""
if after is None:
after = datetime.now(timezone.utc)
scheduled_time = self.get_next_optimal_slot(platform, after)
# Update counters
day_key = scheduled_time.strftime('%Y-%m-%d')
hour_key = scheduled_time.strftime('%Y-%m-%d-%H')
self.posts_per_day[platform][day_key] += 1
self.posts_per_hour[platform][hour_key] += 1
return scheduled_time
def schedule_batch(
self,
posts: List[Dict],
start_after: datetime = None
) -> List[Dict]:
"""
Schedule multiple posts across platforms optimally.
Args:
posts: List of {'content': str, 'platforms': List[str]}
start_after: Earliest scheduling time
Returns:
List of posts with scheduledTime added
"""
if start_after is None:
start_after = datetime.now(timezone.utc)
scheduled_posts = []
platform_last_time: Dict[str, datetime] = {}
for post in posts:
platforms = post['platforms']
# Find the latest "next optimal time" across all platforms
scheduled_time = start_after
for platform in platforms:
platform_name = platform.split('-')[0]
after = platform_last_time.get(platform_name, start_after)
optimal_time = self.schedule_post(platform_name, after)
if optimal_time > scheduled_time:
scheduled_time = optimal_time
platform_last_time[platform_name] = scheduled_time
scheduled_posts.append({
**post,
'scheduledTime': scheduled_time.isoformat()
})
return scheduled_posts
# Usage example
scheduler = OptimalScheduler()
# Posts to schedule
posts = [
{'content': 'Morning motivation!', 'platforms': ['twitter-123', 'linkedin-ABC']},
{'content': 'Product update announcement', 'platforms': ['twitter-123', 'linkedin-ABC', 'instagram-456']},
{'content': 'Behind the scenes', 'platforms': ['instagram-456', 'tiktok-789']},
{'content': 'Weekly tips thread', 'platforms': ['twitter-123']},
{'content': 'Customer success story', 'platforms': ['linkedin-ABC']},
]
scheduled = scheduler.schedule_batch(posts)
print("Optimally scheduled posts:")
for post in scheduled:
print(f" {post['scheduledTime']}: {post['content'][:30]}...")
print(f" Platforms: {post['platforms']}")Node.js - Complete Rate-Limited Queue System
const axios = require('axios');
class PostPostQueueScheduler {
constructor(apiKey) {
this.apiKey = apiKey;
this.api = axios.create({
baseURL: 'https://api.postpost.dev/api/v1',
headers: { 'x-api-key': apiKey }
});
this.platformLimits = {
twitter: { perHour: 10, perDay: 50 },
linkedin: { perHour: 5, perDay: 20 },
instagram: { perHour: 3, perDay: 10 },
tiktok: { perHour: 2, perDay: 10 },
facebook: { perHour: 5, perDay: 25 },
};
this.peakHours = {
twitter: [13, 14, 15, 16],
linkedin: [10, 11, 12],
instagram: [11, 12, 13, 14, 19, 20, 21],
tiktok: [15, 16, 17, 18, 19, 20, 21],
facebook: [13, 14, 15, 16],
};
this.postCounts = { hourly: {}, daily: {} };
}
getPlatformName(platformId) {
return platformId.split('-')[0];
}
getHourKey(date) {
return date.toISOString().slice(0, 13);
}
getDayKey(date) {
return date.toISOString().slice(0, 10);
}
canPostAt(platform, date) {
const limits = this.platformLimits[platform];
if (!limits) return true;
const hourKey = `${platform}-${this.getHourKey(date)}`;
const dayKey = `${platform}-${this.getDayKey(date)}`;
const hourlyCount = this.postCounts.hourly[hourKey] || 0;
const dailyCount = this.postCounts.daily[dayKey] || 0;
return hourlyCount < limits.perHour && dailyCount < limits.perDay;
}
recordPost(platform, date) {
const hourKey = `${platform}-${this.getHourKey(date)}`;
const dayKey = `${platform}-${this.getDayKey(date)}`;
this.postCounts.hourly[hourKey] = (this.postCounts.hourly[hourKey] || 0) + 1;
this.postCounts.daily[dayKey] = (this.postCounts.daily[dayKey] || 0) + 1;
}
isPeakTime(platform, date) {
const hours = this.peakHours[platform];
return hours ? hours.includes(date.getUTCHours()) : true;
}
findNextOptimalSlot(platforms, after) {
let candidate = new Date(after);
candidate.setUTCMinutes(0, 0, 0);
for (let i = 0; i < 168; i++) { // Check up to 1 week
candidate = new Date(candidate.getTime() + 3600000); // +1 hour
const allCanPost = platforms.every(p =>
this.canPostAt(this.getPlatformName(p), candidate)
);
if (!allCanPost) continue;
const anyPeak = platforms.some(p =>
this.isPeakTime(this.getPlatformName(p), candidate)
);
if (anyPeak) {
return candidate;
}
}
// Fallback: return next hour
return new Date(after.getTime() + 3600000);
}
async scheduleOptimally(posts) {
const results = [];
let lastTime = new Date();
for (const post of posts) {
const scheduledTime = this.findNextOptimalSlot(post.platforms, lastTime);
// Record for rate limiting
post.platforms.forEach(p => {
this.recordPost(this.getPlatformName(p), scheduledTime);
});
try {
const { data } = await this.api.post('/create-post', {
content: post.content,
platforms: post.platforms,
scheduledTime: scheduledTime.toISOString()
});
results.push({
success: true,
postGroupId: data.postGroupId,
scheduledTime: scheduledTime.toISOString(),
content: post.content.slice(0, 50)
});
console.log(`Scheduled: ${post.content.slice(0, 30)}... at ${scheduledTime.toISOString()}`);
} catch (error) {
results.push({
success: false,
error: error.response?.data?.message || error.message,
content: post.content.slice(0, 50)
});
}
lastTime = scheduledTime;
// Small delay between API calls
await new Promise(r => setTimeout(r, 100));
}
return results;
}
}
// Usage
const scheduler = new PostPostQueueScheduler('YOUR_API_KEY');
const posts = [
{ content: 'Morning motivation!', platforms: ['twitter-123', 'linkedin-ABC'] },
{ content: 'Product update', platforms: ['twitter-123', 'linkedin-ABC', 'instagram-456'] },
{ content: 'Behind the scenes', platforms: ['instagram-456', 'tiktok-789'] },
];
const results = await scheduler.scheduleOptimally(posts);
console.log(`Scheduled ${results.filter(r => r.success).length} of ${results.length} posts`);Complete Queue-Based Scheduling System
This section provides a production-ready implementation that combines all the concepts: rate limiting, optimal scheduling, error handling, retry logic, and failed post recovery.
Architecture Overview
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Content Queue │───►│ Rate Limiter │───►│ PostPost API │
│ (your posts) │ │ + Scheduler │ │ │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌─────────────────┐
│ Retry Queue │ │ Status Checker │
│ (failed posts) │ │ (polling) │
└──────────────────┘ └─────────────────┘JavaScript - Production Queue Scheduler
const axios = require('axios');
/**
* Production-ready queue-based scheduler for PostPost API
* Features:
* - Platform-specific rate limiting
* - Peak engagement time optimization
* - Exponential backoff retry logic
* - Failed post recovery
* - Comprehensive error handling
*/
class PostPostQueueScheduler {
constructor(apiKey, options = {}) {
this.apiKey = apiKey;
this.baseUrl = options.baseUrl || 'https://api.postpost.dev/api/v1';
this.maxRetries = options.maxRetries || 3;
this.retryDelay = options.retryDelay || 1000;
// API rate limit tracking
this.rateLimitRemaining = Infinity;
this.rateLimitReset = 0;
// Platform-specific limits (posts per hour / per day) - all 10 platforms
this.platformLimits = {
twitter: { perHour: 10, perDay: 50 },
linkedin: { perHour: 5, perDay: 20 },
instagram: { perHour: 3, perDay: 10 },
threads: { perHour: 10, perDay: 50 },
tiktok: { perHour: 2, perDay: 10 },
facebook: { perHour: 5, perDay: 25 },
youtube: { perHour: 2, perDay: 10 },
bluesky: { perHour: 10, perDay: 100 },
mastodon: { perHour: 5, perDay: 50 },
telegram: { perHour: 20, perDay: 300 },
};
// Peak engagement hours (UTC) - all 10 platforms
this.peakHours = {
twitter: [13, 14, 15, 16],
linkedin: [10, 11, 12],
instagram: [11, 12, 13, 14, 19, 20, 21],
threads: [12, 13, 14, 15],
tiktok: [15, 16, 17, 18, 19, 20, 21],
facebook: [13, 14, 15, 16],
youtube: [14, 15, 16, 17, 18],
bluesky: [14, 15, 16, 17],
mastodon: [14, 15, 16, 17],
telegram: [9, 10, 11, 12, 18, 19, 20],
};
// Track scheduled posts per platform
this.postCounts = { hourly: {}, daily: {} };
// Failed posts queue for retry
this.failedQueue = [];
// Results tracking
this.results = [];
}
// ... (rest of the implementation)
}Error Handling
This guide covers how to handle errors from the PostPost API, including HTTP status codes, error response formats, retry strategies, and dealing with partial failures.
Platform Limits
This comprehensive guide documents the limits for all 10 social media platforms supported by PostPost. Understanding these limits is essential for building reliable integrations with the PostPost API.