Create Post
Schedule or immediately queue a post across multiple social media platforms.
Endpoint
POST https://api.postpost.dev/api/v1/create-postHeaders
| Header | Required | Description |
|---|---|---|
x-api-key | Yes | Your API key |
x-postpost-user-id | No | Managed user ID (workspace only) |
x-postpost-client | No | Client identifier (e.g., mcp for MCP integrations). Affects which access controls are checked. |
Content-Type | Yes | application/json |
Note: The API does not include
x-api-keyorx-postpost-user-idin CORS allowed headers, so requests from browser-based clients will fail preflight checks. This API is designed for server-to-server use only.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Post text content (cannot be empty or whitespace-only). Must be a string — non-string truthy values (numbers, objects) will pass validation but cause unexpected behavior. |
platforms | string[] | Yes | Array of platform connection IDs matching /^[a-z]+-[a-zA-Z0-9_-]+$/ (e.g., twitter-123456789, linkedin-ABC123) |
scheduledTime | string | No | ISO 8601 UTC datetime. If omitted, the post is created as a draft. If the scheduled time is in the past, it is silently set to the current time |
platformSettings | object | No | Platform-specific settings object. Keys are platform names, values are setting objects (e.g., { "tiktok": { "viewerSetting": "PUBLIC_TO_EVERYONE" } }). Merged with defaults per platform. Can be a JSON string or object. Per-platform values must be plain objects — arrays, null, or primitive values for individual platform keys are silently ignored. |
Response
Returns HTTP 200 on success (not 201).
{
"success": true,
"postGroupId": "507f1f77bcf86cd799439011"
}Use the postGroupId to track, update, or delete the post.
Posts with Media
Important: When attaching images or videos, create the post as a draft (omit
scheduledTime), upload media, then schedule via Update Post.
1. POST /create-post → Omit scheduledTime (creates draft)
2. POST /get-upload-url → Get pre-signed URL for media
3. PUT {uploadUrl} → Upload file to S3
4. PUT /update-post/:postGroupId → Set scheduledTime to scheduleThis ensures media is fully uploaded before the scheduler processes your post. See Upload Media for details.
Default Platform Settings
When creating via the API, these defaults are applied automatically. If you provide a platformSettings object in the request body, your values are merged with these defaults on a per-platform basis — user-provided fields override the corresponding default fields, while any default fields you omit are preserved. See Default Platform Settings for the full list.
{
"tiktok": {
"viewerSetting": "PUBLIC_TO_EVERYONE",
"allowComments": true,
"allowDuet": false,
"allowStitch": false,
"commercialContent": false,
"brandOrganic": false,
"brandedContent": false
},
"instagram": {
"videoType": "REELS"
},
"youtube": {
"privacy": "public",
"title": ""
},
"threads": {
"replyControl": ""
}
}Note: Only
tiktok,youtube, andthreadskeys are recognized inplatformSettings. Other platform keys (e.g.,
Examples
Schedule a text post to X and LinkedIn
JavaScript (fetch)
const response = await fetch('https://api.postpost.dev/api/v1/create-post', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
body: JSON.stringify({
content: 'Excited to share our new product launch! 🚀 #launch',
platforms: ['twitter-123456789', 'linkedin-ABC123'],
scheduledTime: '2026-03-01T14:00:00.000Z'
})
});
const data = await response.json();
console.log(data.postGroupId);Python (requests)
import requests
response = requests.post(
'https://api.postpost.dev/api/v1/create-post',
headers={
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
json={
'content': 'Excited to share our new product launch! 🚀 #launch',
'platforms': ['twitter-123456789', 'linkedin-ABC123'],
'scheduledTime': '2026-03-01T14:00:00.000Z'
}
)
print(response.json()['postGroupId'])cURL
curl -X POST https://api.postpost.dev/api/v1/create-post \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"content": "Excited to share our new product launch! 🚀 #launch",
"platforms": ["twitter-123456789", "linkedin-ABC123"],
"scheduledTime": "2026-03-01T14:00:00.000Z"
}'Node.js (axios)
const axios = require('axios');
const { data } = await axios.post(
'https://api.postpost.dev/api/v1/create-post',
{
content: 'Excited to share our new product launch! 🚀 #launch',
platforms: ['twitter-123456789', 'linkedin-ABC123'],
scheduledTime: '2026-03-01T14:00:00.000Z'
},
{ headers: { 'x-api-key': 'YOUR_API_KEY' } }
);
console.log(data.postGroupId);Post to all 11 platforms at once
const response = await fetch('https://api.postpost.dev/api/v1/create-post', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
body: JSON.stringify({
content: 'Big announcement dropping tomorrow. Stay tuned.',
platforms: [
'twitter-111', 'linkedin-222', 'instagram-333',
'threads-444', 'tiktok-555', 'youtube-666',
'facebook-777', 'bluesky-888', 'mastodon-999',
'telegram-000', 'pinterest-101'
],
scheduledTime: '2026-03-01T09:00:00.000Z'
})
});Create a draft (no scheduled time)
response = requests.post(
'https://api.postpost.dev/api/v1/create-post',
headers={
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
json={
'content': 'Draft post -- will schedule later',
'platforms': ['twitter-123456789']
}
)Errors
| Status | Error | Cause |
|---|---|---|
| 400 | "Content is required" | Missing content field or content is empty/whitespace-only |
| 400 | "Platforms are required" | platforms field is missing or null |
| 400 | "At least one platform is required" | platforms is an empty array |
| 400 | "Platforms must be an array" | platforms is not an array |
| 400 | "Invalid platforms JSON format" | platforms contains malformed JSON |
| 400 | "Invalid platform ID format: <id>" | Platform ID does not match /^[a-z]+-[a-zA-Z0-9_-]+$/ |
| 400 | "Invalid scheduled time format" | scheduledTime is not a valid ISO 8601 datetime |
| 400 | "Invalid platformSettings JSON" | platformSettings was provided as a string that could not be parsed as valid JSON |
| 400 | "platformSettings must be an object" | platformSettings is provided but is not a plain object (e.g., array, null, or primitive after JSON parsing) |
| 400 | "Invalid x-postpost-user-id" | The x-postpost-user-id header value is not a valid ObjectId format |
| 401 | "API key is required" | Missing x-api-key header |
| 401 | "Invalid API key" | x-api-key value is incorrect or revoked |
| 401 | "Invalid API key owner" | API key exists but the associated workspace/user could not be resolved |
| 403 | "API access is not enabled for this account" | The account's plan does not include API access (e.g., Starter plan or custom plan with apiAccess disabled) |
| 403 | "MCP access is not enabled for this account" | Returned when x-postpost-client: mcp is set but MCP access is not enabled |
| 403 | "Workspace access is not enabled for this key" | The API key does not have workspace/managed-user permissions |
| 403 | "User is not managed by key" | The x-postpost-user-id references a user not managed by this API key |
| 403 | LimitExceededError (structured) | Plan limit reached (see below) |
| 500 | "Failed to create post group" | Unexpected server error |
LimitExceededError (403)
When a plan limit is exceeded, the API returns a structured error response. The error field contains a short label, while the message field contains the full human-readable explanation.
Note: The first time a post limit is exceeded in a given billing month, an email notification is sent to the account owner. This notification is sent only once per month.
{
"error": "Post limit reached",
"code": "POST_LIMIT_REACHED",
"metric": "posts.platform_monthly",
"message": "Monthly post limit reached. Your Pro plan allows 100 platform posts per month.",
"limit": 100,
"used": 100,
"requested": 2,
"remaining": 0,
"periodStart": "2026-03-01T00:00:00.000Z",
"periodEnd": "2026-04-01T00:00:00.000Z",
"planName": "Pro"
}Note: Not all fields are present on every error code. For example,
SCHEDULE_HORIZON_REACHEDreturnsnullforused,requested,remaining,periodStart, andperiodEnd.
Some limit errors include additional context fields:
| Field | Present on | Description |
|---|---|---|
scheduledTime | SCHEDULE_HORIZON_REACHED | The requested scheduled time that exceeded the horizon |
maxScheduledDate | SCHEDULE_HORIZON_REACHED | The furthest date allowed by the current plan |
scope | POST_LIMIT_REACHED, SCHEDULED_POST_LIMIT_REACHED | Present when the limit is connection-scoped (e.g., "connection"). When connection-scoped, top-level used and remaining are null — per-connection values are in blockedPlatforms only. |
blockedPlatforms | POST_LIMIT_REACHED, SCHEDULED_POST_LIMIT_REACHED | Array of objects with platformSelection, used, and remaining fields. Present when scope is connection-level |
overLimitBy | CONNECTIONS_OVER_LIMIT | Number of connections over the plan limit |
disallowedPlatforms | PLATFORM_NOT_AVAILABLE | Array of platform names not available on the current plan |
allowedPlatforms | PLATFORM_NOT_AVAILABLE | Array of platform names available on the current plan |
Possible code values:
| Code | Error value | Description |
|---|---|---|
POST_LIMIT_REACHED | "Post limit reached" | Monthly post limit exceeded |
SCHEDULED_POST_LIMIT_REACHED | "Scheduled post limit reached" | Scheduled post limit exceeded |
SCHEDULE_HORIZON_REACHED | "Schedule horizon reached" | Scheduling too far in the future for current plan |
CONNECTIONS_OVER_LIMIT | "Account over channel limit" | Too many platform connections for current plan |
PLATFORM_NOT_AVAILABLE | "Platform not available" | Platform not available on current plan (e.g., a custom plan with restricted allowedPlatforms). Metric: posts.platform_monthly. |
Post Statuses
After creation, the post group goes through these states:
draft → scheduled → published
→ failed
→ partially_publishedPost group statuses (returned in status field):
- draft: Saved but not scheduled
- scheduled: Will be published at
scheduledTime - published: Successfully posted on all platforms
- failed: Failed on all platforms
- partially_published: Succeeded on some, failed on others
Note: Individual platform records (ScheduledPost) have their own statuses including
pendingandprocessing, which reflect per-platform delivery state. The post groupstatusfield above is a rollup and does not includeprocessing. A separateprocessingStatusfield on the post group tracks whether the post is currently being processed. PossibleprocessingStatusvalues are:pending,processing,finished.
Note: The
processingStatusfield is available in the get-post response but is not included in list-posts responses.