Schedule Post
Schedule posts across multiple platforms
Endpoint
POST /postsRequest
curl -X POST https://api.postcore.dev/posts \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profileKey": "prof_abc123",
"content": "Excited to announce our new feature! 🚀",
"platforms": ["linkedin", "bluesky"],
"scheduledFor": "2025-01-15T14:00:00Z"
}'const response = await fetch('https://api.postcore.dev/posts', {
method: 'POST',
headers: {
'x-api-key': process.env.POSTCORE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
profileKey: 'prof_abc123',
content: 'Excited to announce our new feature! 🚀',
platforms: ['linkedin', 'bluesky'],
scheduledFor: '2025-01-15T14:00:00Z',
}),
});
const data = await response.json();
console.log(`Scheduled ${data.posts.length} posts`);import os
import requests
response = requests.post(
'https://api.postcore.dev/posts',
headers={
'x-api-key': os.getenv('POSTCORE_API_KEY'),
'Content-Type': 'application/json'
},
json={
'profileKey': 'prof_abc123',
'content': 'Excited to announce our new feature! 🚀',
'platforms': ['linkedin', 'bluesky'],
'scheduledFor': '2025-01-15T14:00:00Z'
}
)
data = response.json()
print(f"Scheduled {len(data['posts'])} posts")Request Body
| Field | Type | Required | Description |
|---|---|---|---|
profileKey | string | Yes | Profile to post from |
content | string | Yes | Post content (max 3000 chars) |
platforms | array | Yes | Platforms to post to: ["linkedin"], ["bluesky"], or both |
scheduledFor | string | Yes | ISO 8601 datetime in UTC (must be in future) |
Response
{
"posts": [
{
"postId": "post_abc123",
"platform": "linkedin",
"scheduledFor": "2025-01-15T14:00:00Z",
"createdAt": "2025-01-08T12:00:00Z",
"status": "pending"
},
{
"postId": "post_def456",
"platform": "bluesky",
"scheduledFor": "2025-01-15T14:00:00Z",
"createdAt": "2025-01-08T12:00:00Z",
"status": "pending"
}
],
"usage": {
"month": "January 2025",
"used": 2,
"limit": 10,
"remaining": 8
}
}Status Code: 201 Created
Response Fields
Posts Array:
Each platform gets its own post object:
| Field | Type | Description |
|---|---|---|
postId | string | Unique post identifier |
platform | string | Platform name |
scheduledFor | string | When the post will publish |
createdAt | string | When the post was created |
status | string | Always "pending" for new posts |
Usage Object:
Tracks your monthly post quota:
| Field | Type | Description |
|---|---|---|
month | string | Current month |
used | number | Posts created this month |
limit | number | Monthly limit (10 for Free, 3000 for Pro) |
remaining | number | Posts remaining this month |
Scheduling Rules
Time Format
All times must be in UTC using ISO 8601 format:
Correct:
"scheduledFor": "2025-01-15T14:00:00Z"Incorrect:
"scheduledFor": "2025-01-15 14:00:00" // Missing 'T' and 'Z'
"scheduledFor": "January 15, 2025" // Not ISO 8601Future Time Required
The scheduled time must be in the future:
// Must be any time after now
const scheduledFor = new Date();
scheduledFor.setSeconds(scheduledFor.getSeconds() + 5); // Even 5 seconds works
const isoString = scheduledFor.toISOString();Posts are checked every second, so they'll publish as soon as the scheduled time is reached.
Platform Rate Limits
postcore enforces platform-specific rate limits to comply with each platform's posting rules:
| Platform | Minimum Time Between Posts |
|---|---|
| 60 seconds | |
| Bluesky | 1 second |
These limits apply per profile, per platform. You can schedule to multiple platforms simultaneously, but each platform has its own rate limit.
Rate Limit Example
// Allowed: Different platforms
await schedulePost({
platforms: ["linkedin"],
scheduledFor: "2025-01-15T14:00:00Z",
});
await schedulePost({
platforms: ["bluesky"],
scheduledFor: "2025-01-15T14:00:00Z",
}); // ✅ OK - different platform
// Not allowed: Same platform too close
await schedulePost({
platforms: ["linkedin"],
scheduledFor: "2025-01-15T14:00:00Z",
});
await schedulePost({
platforms: ["linkedin"],
scheduledFor: "2025-01-15T14:00:30Z",
}); // ❌ Error - only 30 seconds apartHandling Rate Limit Errors
{
"error": "RATE_LIMIT_EXCEEDED",
"message": "LinkedIn requires 60 seconds between posts. Next available: 2025-01-15T14:01:00Z",
"platform": "linkedin",
"nextAvailable": "2025-01-15T14:01:00Z"
}Status: 429 Too Many Requests
Solution: Use the nextAvailable time for your next LinkedIn post.
Monthly Limits
Your plan determines how many posts you can create per month:
- Free Plan: 10 posts/month
- Pro Plan: 3,000 posts/month
Important: Limits are based on creation date, not publish date. A post created in January counts toward January's limit, even if scheduled to publish in February.
The usage object in every response shows your current usage.
Monthly Limit Error
{
"error": "MONTHLY_LIMIT_EXCEEDED",
"message": "Monthly limit of 10 posts reached",
"limit": 10,
"used": 10
}Status: 429 Too Many Requests
Solution: Wait until next month or upgrade to Pro.
Character Limits
Each platform has its own character limits:
| Platform | Character Limit |
|---|---|
| 3,000 characters | |
| Bluesky | 300 characters |
If your content exceeds a platform's limit, the API will reject the entire request before creating any posts.
Content Too Long Error
{
"error": "CONTENT_TOO_LONG",
"message": "Content exceeds Bluesky's limit of 300 characters (current: 350)",
"platform": "bluesky",
"limit": 300,
"current": 350
}Status: 400 Bad Request
Solution: Shorten your content or remove that platform from the request.
Error Responses
Profile Not Found
{
"error": "PROFILE_NOT_FOUND",
"message": "Profile not found"
}Status: 404 Not Found
Causes:
- Invalid
profileKey - Profile was deleted
- Profile belongs to different account
Platforms Not Connected
{
"error": "PLATFORMS_NOT_CONNECTED",
"message": "The following platforms are not connected: linkedin",
"missingPlatforms": ["linkedin"]
}Status: 400 Bad Request
Solution: Connect the missing platforms first. See Connect OAuth Platforms or Connect Credential Platforms.
Invalid Scheduled Time
{
"error": "INVALID_SCHEDULED_TIME",
"message": "Scheduled time must be in the future"
}Status: 400 Bad Request
Solution: Schedule any time in the future (even a few seconds works).
Best Practices
Respect Rate Limits
Track next available posting times for each platform:
const rateLimits = {
linkedin: 60000, // 60 seconds in ms
bluesky: 1000, // 1 second in ms
};
let nextAvailable = {
linkedin: new Date(),
bluesky: new Date(),
};
async function schedulePost(profileKey, content, platforms) {
// Find the latest required time across requested platforms
const earliestTime = platforms.reduce((latest, platform) => {
const platformTime = nextAvailable[platform];
return platformTime > latest ? platformTime : latest;
}, new Date());
// Ensure it's in the future
if (earliestTime <= new Date()) {
earliestTime.setSeconds(earliestTime.getSeconds() + 1);
}
const response = await fetch("https://api.postcore.dev/posts", {
method: "POST",
headers: {
"x-api-key": process.env.POSTCORE_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
profileKey,
content,
platforms,
scheduledFor: earliestTime.toISOString(),
}),
});
if (response.ok) {
// Update next available times
platforms.forEach((platform) => {
nextAvailable[platform] = new Date(
earliestTime.getTime() + rateLimits[platform]
);
});
}
return response.json();
}Monitor Monthly Usage
Check usage before scheduling:
const response = await fetch("https://api.postcore.dev/posts", {
method: "POST",
headers: {
"x-api-key": apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
/* ... */
}),
});
const { usage } = await response.json();
if (usage.remaining < 5) {
console.warn(`Only ${usage.remaining} posts remaining this month!`);
}Validate Content Length
Check content length before sending:
function validateContent(content, platforms) {
const limits = { linkedin: 3000, bluesky: 300 };
for (const platform of platforms) {
if (content.length > limits[platform]) {
throw new Error(
`Content too long for ${platform}: ${content.length}/${limits[platform]} chars`
);
}
}
}
// Usage
try {
validateContent(myContent, ["linkedin", "bluesky"]);
await schedulePost(/* ... */);
} catch (error) {
console.error(error.message);
}