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
/api/v1/postsCreates 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
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
| Field | Type | Notes |
|---|---|---|
caption | string, 1–5000 | Default caption for every channel that doesn't override. |
channels[] | array, 1–25 | Each item targets one connected account. |
channels[].id | uuid | From GET /channels. |
channels[].caption | string, optional | Per-channel override. Falls back to top-level caption. |
channels[].title | string, optional | YouTube-only. Required for YouTube channels. |
channels[].description | string, optional | YouTube description; ignored on other platforms. |
channels[].options | object, optional | Per-platform publish options — see the table below. Unknown keys are a 400; known keys on the wrong platform are a 422. |
media[] | array, 0–10 | Either { id } (pre-uploaded via POST /media) or { url } (server fetches; HTTPS only). |
scheduled_for | ISO 8601, optional | Schedules 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.
{
"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 (
422otherwise) — drafts may stay media-less. - The scheduled path consumes one post-quota slot and respects the pending-schedule cap: breaches return
402 quota_exceededwithused / limit / reset_at / upgrade_urlinerror.details. POST /draftsrejectsscheduled_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.
| Platform | Option | Type | Notes |
|---|---|---|---|
| YouTube | privacy_status | public | unlisted | private | Defaults to public. |
| YouTube | category_id | string | Numeric category id ("22" = People & Blogs). |
| YouTube | tags | string[], ≤50 | Combined length ≤ 500 characters. |
| YouTube | made_for_kids | boolean | COPPA self-declaration. |
| YouTube | contains_synthetic_media | boolean | AI / synthetic-media disclosure. |
| Facebook + Instagram | share_to_story | boolean | Also 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 + Instagram | image_alt_texts | string[], ≤10 | One alt text per attached image, by media order. ≤1000 chars each. |
share_to_feed | boolean | Reels only. false keeps the reel out of the main feed/grid. | |
collaborators | string[], ≤3 | Usernames invited as collaborators; they must accept the invite. | |
video_title | string, ≤255 | Title on video posts. |
Response — 202 Accepted
{
"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
/api/v1/posts/{id}Returns the same shape as the create response, refreshed from the database.
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.