Posts

Create and schedule a social media post

Changed on

Create a post to be published across one or more social media platforms. Supports text, images, and video (1 video per post). The image count per post is platform-specific: Twitter/X, Bluesky, and Mastodon allow up to 4; Instagram, LinkedIn, Facebook, and Telegram allow up to 10; Threads allows up to 20. There is no separate "carousel" field — a carousel is formed implicitly by attaching multiple images to one postGroupId (Instagram: 2-10 images = carousel, 1 image = single photo), in upload order. See the Media Uploads guide for the full matrix. If scheduledTime is provided, the post will be published at that time. If omitted, the post is saved as a draft. Set publishNow=true to publish immediately at server time.

post/create-post

Request

  • Base URL
    https://api.publora.com/api/v1
  • Auth
    API key in header x-publora-key

Headers

x-publora-user-idstring

Managed user ID (workspace only)

Idempotency-Keystring

Opt-in idempotency. Omit the header for the previous behaviour — every call creates a new post.

Send a unique value (a UUID is a good choice) per logical operation to make retries safe:

  • Same key + identical body, original request finished → the original status code and response body are replayed. No second post.
  • Same key + identical body, original request still in flight → 409 (IDEMPOTENCY_IN_FLIGHT). Retry shortly.
  • Same key + different body → 422 (IDEMPOTENCY_KEY_CONFLICT). Nothing is created.

Keys are scoped to the acting user (the managed user when x-publora-user-id is set), so they never collide across accounts. Records expire 24 hours after they are created; reusing a key after that window is treated as a brand-new request.

Request body

contentstring

Normally required and non-empty. May be omitted or empty only when a targeted LinkedIn connection has repost intent via platformSettings.linkedin.repostEnabled=true or a non-empty repostParentUrn; repost validation still requires a valid parent/setting combination.

platformsstring[] required

Array of platform connection IDs (format: platform-platformId). Each ID must appear at most once; a repeated ID is rejected with 400 "Platforms must not contain duplicates".

scheduledTimestring date-time

ISO 8601 UTC datetime for scheduling. Omit for draft.

If the time is in the past, it is clamped to the current server time and a SCHEDULED_TIME_COERCED warning is returned — the post is still created. Read the scheduledTime in the response for the time actually stored.

Times less than 5 minutes in the past are always tolerated this way. Strict rejection for a time 5 or more minutes in the past is scheduled to begin on 2026-08-25, unless production configuration overrides that date. Before strict mode it is clamped with a warning. Send a future time to avoid both.

publishNowboolean

true publishes immediately. The server uses its current time as scheduledTime, stores the post as scheduled and starts delivery at once; the response does not wait for the platforms. The usual connection, plan, content, media and first-comment validation applies. Publish Now uses the monthly post allowance (POST_LIMIT_REACHED) but takes no slot in the scheduled-post queue and ignores the schedule horizon. Combining it with a future scheduledTime or status: "draft" returns 400 PUBLISH_NOW_CONFLICT; a non-boolean value returns 400 INVALID_PUBLISH_NOW. Omitted or false keeps draft/schedule behavior.

mediaUrlsstring[]

Public https URLs downloaded server-side. Images: 25 MB each; videos: 150 MB each; aggregate: 300 MB. Ingestion is all-or-nothing.

Example request

{
  "content": "Excited to share our new product launch! 🚀 #launch",
  "platforms": [
    "twitter-123456789",
    "linkedin-ABC123"
  ],
  "scheduledTime": "2027-03-01T14:00:00.000Z",
  "publishNow": true,
  "platformSettings": {
    "instagram": {
      "coverUrl": "https://cdn.example.com/covers/reel-cover.jpg",
      "cover_url": "https://cdn.example.com/covers/reel-cover.jpg"
    },
    "youtube": {
      "playlist": {
        "id": "PLxxxxxxxx",
        "platformId": "youtube-UCxxxxxxxx"
      },
      "thumbnail": {
        "mediaId": "665f...",
        "url": "https://media.publora.com/..."
      }
    },
    "twitter": {
      "replyTo": "https://x.com/customer/status/1234567890123456789",
      "quoteTweet": "987654321098765432"
    }
  },
  "firstComment": {
    "text": "Full write-up with the numbers: https://example.com/launch-week",
    "platforms": [
      "linkedin"
    ]
  }
}

Response

Post created

successboolean
postGroupIdstring
scheduledTimestring date-time nullable

The effective scheduled time that was actually stored. Always present. null when the post was saved as a draft (no scheduledTime sent). This may differ from the value you sent — if so, a SCHEDULED_TIME_COERCED warning explains why. Trust this field over your requested value.

Example response

{
  "success": true,
  "postGroupId": "507f1f77bcf86cd799439011",
  "scheduledTime": "2026-03-01T14:00:00.000Z",
  "warnings": [
    {
      "code": "SCHEDULED_TIME_COERCED",
      "message": "Requested scheduled time 2026-03-01T14:00:00.000Z was in the past and was changed to server time 2026-03-01T14:02:11.412Z.",
      "requested": "2026-03-01T14:00:00.000Z",
      "effective": "2026-03-01T14:02:11.412Z",
      "platforms": [
        "instagram"
      ]
    }
  ]
}

Changes

    • info

      added the new optional request property //

    • info

      added the new optional request property //

    • info

      added the new optional request property //

    • info

      added the new optional request property //

    • info

      added the new optional request property //