Developers

Posts

Create a post and read its status. Drafts by default; pass scheduled_for to schedule it for real. See the policy note.

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 the full set of per-platform publish options the dashboard composer offers (YouTube privacy, category, tags and disclosures; Instagram story / feed / collaborators; Facebook story / video title; TikTok audience and compliance toggles; LinkedIn visibility and reshare control; image alt text on Meta and LinkedIn).

Media is optional where the platform allows it: text-only posts publish fine to Facebook, LinkedIn, and webhooks, while Instagram, TikTok, and YouTube need media. Check each channel's accepts block from GET /channels before composing.

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. A publishing YouTube target needs a title or a caption to derive one from; drafts may omit it.
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–20Either { 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.
publish_nowboolean, optionalPublishes immediately instead of drafting. See Publish now below. Mutually exclusive with scheduled_for.

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. It's 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…" } ]
}
  • Content rules are per-platform (422 when a target can't publish the shape): text-only posts are fine on Facebook, LinkedIn, and webhooks; Instagram, TikTok, and YouTube targets need media. Drafts stay lenient while being assembled.
  • 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.
  • To keep something draft-only, omit both scheduled_for and publish_now when creating. You can publish it later by patching either field in — see PATCH /posts/{id}.

Publish now

Pass publish_now: true and the post is created with status: queued and handed straight to the worker, with no delay and no review step. It's the immediate counterpart to scheduling.

json
{
  "caption": "Live now 🎙️",
  "publish_now": true,
  "channels": [ { "id": "5a1d…" } ],
  "media": [ { "id": "9f3a…" } ]
}
  • Mutually exclusive with scheduled_for. Sending both is a 422 unprocessable.
  • The same per-platform content rules as scheduling apply: text-only is fine on Facebook, LinkedIn, and webhooks; Instagram/TikTok/YouTube need media (422 otherwise).
  • Consumes one post-quota slot and respects the same caps; breaches return 402 quota_exceeded with used / limit / reset_at / upgrade_url in error.details.
  • The create response returns status: queued immediately; poll GET /posts/{id} for per-channel publish status.

Per-platform options

Supported on youtube, meta_instagram, meta_facebook, tiktok, linkedin_personal and linkedin_company channels — the same settings the dashboard composer exposes. custom_webhook has no options. TikTok and LinkedIn currently publish to existing connections only: current plans don't offer new connections to them, so their options apply to channels connected earlier.

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, so a story failure never fails the post.
Facebook, Instagram + LinkedInimage_alt_textsstring[], ≤20One 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.
TikTokprivacy_levelPUBLIC_TO_EVERYONE | MUTUAL_FOLLOW_FRIENDS | FOLLOWER_OF_CREATOR | SELF_ONLYRequired when publishing to a direct-post channel (accepts.direct_post: true) — TikTok forbids a preselected default. Inbox-mode channels ignore it.
TikTokdisable_comment / disable_duet / disable_stitchbooleanDirect post only. Turn off comments, Duets, or Stitches.
TikTokcommercial_contentbooleanDirect post only. When true, at least one of brand_organic_toggle / brand_content_toggle must be set (422 otherwise).
TikTokbrand_organic_toggle / brand_content_togglebooleanYour-brand / branded-content disclosures. Branded content can't combine with privacy_level: SELF_ONLY.
TikTokis_aigcbooleanAI-generated-content disclosure.
LinkedInvisibilityPUBLIC | CONNECTIONSWho can see the post; defaults to PUBLIC.
LinkedInreshare_disabledbooleanBlock reposts of this post.

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/app/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."
  }
}

List posts

GET/api/v1/posts

Lists the workspace's posts (drafts, scheduled, and published) newest first, cursor-paginated. Each item is the same shape as the create response. Scope posts:read.

bash
curl "https://postme.live/api/v1/posts?status=queued&scheduled=true&limit=50" \
  -H "Authorization: Bearer pml_live_..."

Filters

All optional; combine freely. They're AND-ed together and applied on top of cursor paging.

ParameterTypeNotes
statusenumOne of draft, queued, posting, partial, posted, failed. Anything else is a 400.
scheduledtrueRestricts to pending scheduled posts only (a future scheduled_for that is still queued).
channel_iduuidOnly posts targeting this channel.
before / afterISO 8601Bound by creation time: created_at strictly before / after the given instant. A malformed value is a 400.

Returns the standard { data, has_more, next_cursor } envelope.

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.

Edit a draft

PATCH/api/v1/posts/{id}

Edit a draft in place. Every field is optional and replace-on-present: a field you omit stays as it was; a field you send overwrites it. Send at least one. Scope posts:write; requires an Idempotency-Key like every write.

  • caption replaces the base caption (and propagates to any channel that didn't set its own override).
  • channels replaces the whole target set, including per-channel captions and options. Omit it to keep the current targets; it can't be sent empty.
  • media replaces attachments. [] clears them; omitting media keeps them.
bash
# Revise a draft's copy and swap its image
curl -X PATCH https://postme.live/api/v1/posts/7d0a8b1c-2e3f-4a5b-9c0d-1e2f3a4b5c6d \
  -H "Authorization: Bearer pml_live_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "caption": "Tightened headline ✨",
    "media": [{ "id": "9f3a…" }]
  }'

Publish a draft

Add scheduled_for (ISO 8601, ≥ 2 minutes ahead) or publish_now: true to the same call to publish the draft you created earlier — this is the only way to publish an existing draft. It behaves exactly like POST /posts: mutually exclusive, consumes one post-quota slot (402 on breach), and enforces the per-platform content rules (422 on an unpublishable combination).

bash
# Schedule the same draft to go out later
curl -X PATCH https://postme.live/api/v1/posts/7d0a8b1c-2e3f-4a5b-9c0d-1e2f3a4b5c6d \
  -H "Authorization: Bearer pml_live_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "scheduled_for": "2026-07-06T15:00:00Z" }'

Returns 200 OK with the updated post (same shape as create). Only composed drafts are editable: a post that is already scheduled, publishing, published, or failed returns 409 — unschedule a scheduled post first with DELETE /posts/{id}. discovered posts (source: "discovered") are read-only and also return 409. 404 when the post isn't yours.

Delete a draft, or unschedule a scheduled post

DELETE/api/v1/posts/{id}

Exactly two outcomes, reported by action in the response: a draft is permanently deleted, and a queued (scheduled or publishing-soon) post is unscheduled — its pending publish is cancelled and the post reverts to a draft, same as "Cancel scheduled" in the dashboard. The composed content survives as a draft. Everything else returns 409: posting/partial/posted are immutable publish history, failed posts keep their per-channel error record, and discovered posts mirror content published outside postme. Published and discovered posts can never be deleted — this endpoint never removes content from the social platforms. Scope posts:write; like every write it requires an Idempotency-Key.

bash
curl -X DELETE https://postme.live/api/v1/posts/7d0a8b1c-2e3f-4a5b-9c0d-1e2f3a4b5c6d \
  -H "Authorization: Bearer pml_live_..." \
  -H "Idempotency-Key: $(uuidgen)"

Response: 200 OK

json
{
  "id": "7d0a8b1c-2e3f-4a5b-9c0d-1e2f3a4b5c6d",
  "action": "unscheduled",
  "deleted": false,
  "previous_status": "queued"
}
  • action: deleted — the draft row is gone. action: unscheduled — the post is back to status: draft; edit and reschedule it any time.
  • Unscheduling gives no quota refund; the consumed post slot stays spent (same as the dashboard).
  • 404 not_found when the post doesn't exist or isn't yours; 409 conflict when it is publishing, published, failed, or a discovered post.
Posts: Public API