Skip to main content
DocsPlatformsAPI ReferenceAI & Integrations
Posts

Create Post

Publish content to one or more social platforms with a single API call.

Endpoint

POST https://api.postpeer.dev/v1/posts

Request Body

To save a post without publishing or scheduling it, send the normal create-post request with saveAsDraft: true; drafts require content or media and at least one platform target, consume no credits, and can later be updated or published with PUT /v1/posts/:postId.

FieldTypeRequiredDescription
contentstringYesThe text body of your post
publishNowbooleanYes*Set to true to publish immediately
saveAsDraftbooleanNoSet to true to save without publishing or scheduling; cannot be combined with publishNow: true or scheduledFor.
scheduledForstringNoISO 8601 datetime for scheduled posts
timezonestringNoIANA timezone (default: "UTC")
idempotencyKeystringNoUnique key to safely retry the request. See Idempotency.

*Required when neither scheduledFor nor saveAsDraft: true is provided.

platforms (required)

An array of platform targets (min 1).

FieldTypeRequiredDescription
platformstringYesOne of the supported platform values below
accountIdstringYesIntegration ID from /v1/connect/integrations
contentstringNoPer-platform text override. When set, this replaces the top-level content for this platform only. Useful when different platforms call for different copy (e.g. hashtags on Instagram, shorter on Twitter).
platformSpecificDataobjectNoPlatform-specific options (see Platforms)

Supported values: "twitter", "instagram", "youtube", "tiktok", "pinterest", "linkedin", "bluesky", "facebook", "threads", "googlebusiness".

mediaItems (optional)

An array of media attachments.

FieldTypeRequiredDescription
typestringYes"image", "video", or "gif"
urlstringYesPublic URL to the media file
thumbnailstringNoVideo thumbnail URL (Facebook and YouTube)

Example: Publish Now

curl -X POST "https://api.postpeer.dev/v1/posts" \
  -H "x-access-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Launching our new feature today!",
    "platforms": [
      { "platform": "twitter", "accountId": "abc123" },
      { "platform": "instagram", "accountId": "def456" }
    ],
    "mediaItems": [
      { "type": "image", "url": "https://example.com/image.png" }
    ],
    "publishNow": true
  }'
import PostPeer from '@postpeer/node';

const client = new PostPeer();
const { data } = await client.posts.create({
	body: {
		content: 'Launching our new feature today!',
		platforms: [
			{ platform: 'twitter', accountId: 'abc123' },
			{ platform: 'instagram', accountId: 'def456' },
		],
		mediaItems: [{ type: 'image', url: 'https://example.com/image.png' }],
		publishNow: true,
	},
});
from postpeer import PostPeer

with PostPeer() as client:
    post = client.posts.create(
        content="Launching our new feature today!",
        platforms=[
            {"platform": "twitter", "accountId": "abc123"},
            {"platform": "instagram", "accountId": "def456"},
        ],
        media_items=[
            {"type": "image", "url": "https://example.com/image.png"},
        ],
        publish_now=True,
    )

Example: Cross-Post to Multiple Platforms

curl -X POST "https://api.postpeer.dev/v1/posts" \
  -H "x-access-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Check out our latest video!",
    "platforms": [
      { "platform": "twitter", "accountId": "tw_123" },
      { "platform": "youtube", "accountId": "yt_456", "platformSpecificData": { "title": "Our Latest Video", "visibility": "public" } }
    ],
    "mediaItems": [
      { "type": "video", "url": "https://example.com/video.mp4" }
    ],
    "publishNow": true
  }'
const { data } = await client.posts.create({
	body: {
		content: 'Check out our latest video!',
		platforms: [
			{ platform: 'twitter', accountId: 'tw_123' },
			{
				platform: 'youtube',
				accountId: 'yt_456',
				platformSpecificData: {
					title: 'Our Latest Video',
					visibility: 'public',
				},
			},
		],
		mediaItems: [{ type: 'video', url: 'https://example.com/video.mp4' }],
		publishNow: true,
	},
});
post = client.posts.create(
    content="Check out our latest video!",
    platforms=[
        {"platform": "twitter", "accountId": "tw_123"},
        {
            "platform": "youtube",
            "accountId": "yt_456",
            "platformSpecificData": {
                "title": "Our Latest Video",
                "visibility": "public",
            },
        },
    ],
    media_items=[
        {"type": "video", "url": "https://example.com/video.mp4"},
    ],
    publish_now=True,
)

Example: Per-Platform Content Override

Each platform entry accepts its own content field. When provided, it replaces the top-level content for that platform only — handy when you want a punchy tweet but a longer caption on Instagram.

curl -X POST "https://api.postpeer.dev/v1/posts" \
  -H "x-access-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Shipped: faster scheduling, fewer clicks. 🚀",
    "platforms": [
      {
        "platform": "twitter",
        "accountId": "tw_123"
      },
      {
        "platform": "linkedin",
        "accountId": "li_789",
        "content": "Excited to share that we just shipped a major update to our scheduling engine. The team focused on two things: cutting time-to-publish and reducing the number of clicks it takes to get a post live across multiple channels. If you manage social for a brand or team, I would love your feedback."
      }
    ],
    "publishNow": true
  }'
const { data } = await client.posts.create({
	body: {
		content: 'Shipped: faster scheduling, fewer clicks. 🚀',
		platforms: [
			{ platform: 'twitter', accountId: 'tw_123' },
			{
				platform: 'linkedin',
				accountId: 'li_789',
				content:
					'Excited to share that we just shipped a major update to our scheduling engine. The team focused on two things: cutting time-to-publish and reducing the number of clicks it takes to get a post live across multiple channels. If you manage social for a brand or team, I would love your feedback.',
			},
		],
		publishNow: true,
	},
});
post = client.posts.create(
    content="Shipped: faster scheduling, fewer clicks. 🚀",
    platforms=[
        {"platform": "twitter", "accountId": "tw_123"},
        {
            "platform": "linkedin",
            "accountId": "li_789",
            "content": (
                "Excited to share that we just shipped a major update to our "
                "scheduling engine. The team focused on two things: cutting "
                "time-to-publish and reducing the number of clicks it takes to "
                "get a post live across multiple channels. If you manage social "
                "for a brand or team, I would love your feedback."
            ),
        },
    ],
    publish_now=True,
)

Twitter falls back to the top-level content, while LinkedIn gets its own longer, more professional version.

Response

Publish-now requests return final platform results when the worker completes within the request wait window:

{
	"success": true,
	"status": "published",
	"postId": "post_abc123",
	"platforms": [
		{
			"platform": "twitter",
			"success": true,
			"platformPostUrl": "https://twitter.com/you/status/123456"
		},
		{
			"platform": "instagram",
			"success": true,
			"platformPostUrl": "https://instagram.com/p/abc123"
		}
	]
}

If the publish worker is still processing when that wait window expires, the request still returns 202 with the saved postId. Poll GET /v1/posts/{postId} or list posts to read the final platform URLs and errors.

{
	"success": true,
	"status": "publishing",
	"postId": "post_abc123",
	"platforms": [
		{ "platform": "twitter", "success": true },
		{ "platform": "instagram", "success": true }
	]
}

Status Codes

CodeMeaning
200Idempotent replay — the original post for this idempotencyKey is returned
202Post accepted — pending, publishing, published, scheduled, partial, or failed
400Validation error (bad request body)
402Not enough credits
403Forbidden
503Publishing or scheduling unavailable

Idempotency

Publishing is synchronous, so a slow platform can push a request past your HTTP client's timeout. If your client then retries, you risk publishing the same post twice.

To make retries safe, send an idempotencyKey: any unique string you generate per post (a UUID works well). The first request with a given key creates and publishes the post as normal. Any later request that reuses the same key skips publishing entirely and returns the original post's result with a 200 status instead of 202. No extra credits are charged.

The key is scoped to your project and never expires. Use a fresh key for each distinct post; reusing a key always returns the first post it was attached to. The field is fully optional. Leave it out and posts behave exactly as before.

curl -X POST "https://api.postpeer.dev/v1/posts" \
  -H "x-access-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Launching our new feature today!",
    "platforms": [
      { "platform": "twitter", "accountId": "abc123" }
    ],
    "publishNow": true,
    "idempotencyKey": "launch-2026-08-29-tweet"
  }'

Retrying that exact request (same idempotencyKey) returns the post created by the first call rather than posting again.

Partial Failures

When posting to multiple platforms, some may succeed and others fail. A 202 response with "status": "partial" includes per-platform results:

{
	"success": true,
	"status": "partial",
	"postId": "post_abc123",
	"platforms": [
		{ "platform": "twitter", "success": true, "platformPostUrl": "..." },
		{
			"platform": "instagram",
			"success": false,
			"error": "Image URL is not publicly accessible"
		}
	]
}

Failed platform posts don't consume credits.

On this page