PostPost

Create Post

Schedule or immediately queue a post across multiple social media platforms.

Endpoint

POST https://api.postpost.dev/api/v1/create-post

Headers

HeaderRequiredDescription
x-api-keyYesYour API key
x-postpost-user-idNoManaged user ID (workspace only)
x-postpost-clientNoClient identifier (e.g., mcp for MCP integrations). Affects which access controls are checked.
Content-TypeYesapplication/json

Note: The API does not include x-api-key or x-postpost-user-id in 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

ParameterTypeRequiredDescription
contentstringYesPost 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.
platformsstring[]YesArray of platform connection IDs matching /^[a-z]+-[a-zA-Z0-9_-]+$/ (e.g., twitter-123456789, linkedin-ABC123)
scheduledTimestringNoISO 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
platformSettingsobjectNoPlatform-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 schedule

This 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, instagram, youtube, and threads keys are recognized in platformSettings. Other platform keys (e.g., twitter, linkedin) are silently ignored and dropped.

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

StatusErrorCause
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
403LimitExceededError (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_REACHED returns null for used, requested, remaining, periodStart, and periodEnd.

Some limit errors include additional context fields:

FieldPresent onDescription
scheduledTimeSCHEDULE_HORIZON_REACHEDThe requested scheduled time that exceeded the horizon
maxScheduledDateSCHEDULE_HORIZON_REACHEDThe furthest date allowed by the current plan
scopePOST_LIMIT_REACHED, SCHEDULED_POST_LIMIT_REACHEDPresent 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.
blockedPlatformsPOST_LIMIT_REACHED, SCHEDULED_POST_LIMIT_REACHEDArray of objects with platformSelection, used, and remaining fields. Present when scope is connection-level
overLimitByCONNECTIONS_OVER_LIMITNumber of connections over the plan limit
disallowedPlatformsPLATFORM_NOT_AVAILABLEArray of platform names not available on the current plan
allowedPlatformsPLATFORM_NOT_AVAILABLEArray of platform names available on the current plan

Possible code values:

CodeError valueDescription
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_published

Post 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 pending and processing, which reflect per-platform delivery state. The post group status field above is a rollup and does not include processing. A separate processingStatus field on the post group tracks whether the post is currently being processed. Possible processingStatus values are: pending, processing, finished.

Note: The processingStatus field is available in the get-post response but is not included in list-posts responses.


On this page