Create a post

POST/api/v1/posts

Creates one post targeting one or more channels. Each channel can use the top-level caption verbatim, or provide its own override. YouTube channels accept title and description in addition, and channels[].options carries per-platform publish options (YouTube privacy, category, tags and disclosures; Instagram story / feed / collaborators; Facebook story / video title; image alt text on both Meta platforms).

Request

http
POST /api/v1/posts HTTP/1.1
Host: postme.live
Authorization: Bearer pml_live_AbCdEf123456_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Idempotency-Key: 6f4b1c8e-3e5e-4b9c-9b4f-0e0e3c1f9a2c

{
  "caption": "We just shipped v0.20!",
  "channels": [
    { "id": "5a1d…", "caption": "Big day! 🚀 #shipping",
      "options": { "share_to_story": true, "collaborators": ["teammate"] } },
    { "id": "6b2e…", "title": "What we shipped in v0.20", "description": "Long-form notes…",
      "options": { "privacy_status": "unlisted", "tags": ["changelog", "shipping"] } }
  ],
  "media": [
    { "id": "9f3a…" },
    { "url": "https://example.com/hero.jpg" }
  ]
}

Field reference

FieldTypeNotes
captionstring, 1–5000Default caption for every channel that doesn't override.
channels[]array, 1–25Each item targets one connected account.
channels[].iduuidFrom GET /channels.
channels[].captionstring, optionalPer-channel override. Falls back to top-level caption.
channels[].titlestring, optionalYouTube-only. Required for YouTube channels.
channels[].descriptionstring, optionalYouTube description; ignored on other platforms.
channels[].optionsobject, optionalPer-platform publish options — see the table below. Unknown keys are a 400; known keys on the wrong platform are a 422.
media[]array, 0–10Either { id } (pre-uploaded via POST /media) or { url } (server fetches; HTTPS only).
scheduled_forISO 8601, optionalSchedules the post instead of drafting it — see Scheduling below.

Scheduling

Pass scheduled_for (ISO 8601, at least 2 minutes in the future) and the post is created with status: queued and publishes automatically at that time — the same delayed job the dashboard composer uses, so you can still cancel it from the dashboard ("Cancel scheduled"). Omit the field for the classic draft-only behavior.

json
{
  "caption": "Going live Friday 9am 🎙️",
  "scheduled_for": "2026-06-19T23:00:00Z",
  "channels": [ { "id": "5a1d…" } ],
  "media": [ { "id": "9f3a…" } ]
}
  • Scheduled posts require at least one media item (422 otherwise) — drafts may stay media-less.
  • The scheduled path consumes one post-quota slot and respects the pending-schedule cap: breaches return 402 quota_exceeded with used / limit / reset_at / upgrade_url in error.details.
  • POST /drafts rejects scheduled_for — use it when an automation must never schedule.
  • Immediate "post now" is still deferred; the 2-minute floor keeps the API scheduling-only in v1.

Per-platform options

Supported on youtube, meta_instagram and meta_facebook channels. Other platforms don't accept options yet.

PlatformOptionTypeNotes
YouTubeprivacy_statuspublic | unlisted | privateDefaults to public.
YouTubecategory_idstringNumeric category id ("22" = People & Blogs).
YouTubetagsstring[], ≤50Combined length ≤ 500 characters.
YouTubemade_for_kidsbooleanCOPPA self-declaration.
YouTubecontains_synthetic_mediabooleanAI / synthetic-media disclosure.
Facebook + Instagramshare_to_storybooleanAlso share the media to Stories after the post/reel publishes. Stories carry no caption or link (Meta API limit); best-effort — a story failure never fails the post.
Facebook + Instagramimage_alt_textsstring[], ≤10One alt text per attached image, by media order. ≤1000 chars each.
Instagramshare_to_feedbooleanReels only. false keeps the reel out of the main feed/grid.
Instagramcollaboratorsstring[], ≤3Usernames invited as collaborators; they must accept the invite.
Facebookvideo_titlestring, ≤255Title on video posts.

Response — 202 Accepted

json
{
  "id": "7d0a…",
  "status": "draft",
  "caption": "We just shipped v0.20!",
  "channels": [
    { "id": "1…", "channel_id": "5a1d…", "platform": "meta_instagram", "status": "pending",
      "external_post_id": null, "external_post_url": null, "error_message": null,
      "options": { "share_to_story": true, "collaborators": ["teammate"] } },
    { "id": "2…", "channel_id": "6b2e…", "platform": "youtube", "status": "pending",
      "external_post_id": null, "external_post_url": null, "error_message": null,
      "options": { "privacy_status": "unlisted", "tags": ["changelog", "shipping"] } }
  ],
  "media_ids": ["9f3a…", "ab12…"],
  "scheduled_for": null,
  "review_url": "https://postme.live/post?draft=7d0a…",
  "created_at": "2026-05-15T08:11:19Z",
  "updated_at": "2026-05-15T08:11:19Z",
  "meta": {
    "note": "v1 API: post persisted as draft. Review and publish from the dashboard."
  }
}

Get a post

GET/api/v1/posts/{id}

Returns the same shape as the create response, refreshed from the database.

bash
curl https://postme.live/api/v1/posts/7d0a… \
  -H "Authorization: Bearer pml_live_..."

Returns 404 not_found when the post does not exist or belongs to a different organization than the API key.

Posts — Public API