Posts & Publishing
Create posts and publish them to connected social platforms.
Create Post
POST /api/v1/posts
Create a post from generation outputs or custom content. Posts start as drafts that you can review before publishing.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
generation_id | string | No | Caption generation ID to populate content from |
items | array | Yes | Post items — one per post type you want to publish (1–12, matching the supported post types) |
Post Item Object
| Field | Type | Required | Description |
|---|---|---|---|
post_type | string | Yes | Target post type (e.g., instagram-reel, tiktok-video) |
content | string | Yes* | Caption text for this platform |
title | string | No | Title (required for YouTube post types) |
media_ids | string[] | No | Media to attach (uploaded or generated images) |
* If generation_id is provided, content is optional and will be filled from the generation outputs.
Examples
From a Generation
Create a post using outputs from a completed caption generation:
curl -X POST https://powerpost.ai/api/v1/posts \
-H "x-api-key: pp_live_sk_YOUR_KEY" \
-H "X-Workspace-Id: YOUR_WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"generation_id": "550e8400-e29b-41d4-a716-446655440000",
"items": [
{
"post_type": "instagram-reel",
"media_ids": ["img-001-abcd-efgh"]
},
{
"post_type": "tiktok-video",
"media_ids": ["img-001-abcd-efgh"]
}
]
}'const res = await fetch('https://powerpost.ai/api/v1/posts', {
method: 'POST',
headers: {
'x-api-key': 'pp_live_sk_YOUR_KEY',
'X-Workspace-Id': 'YOUR_WORKSPACE_ID',
'Content-Type': 'application/json',
},
body: JSON.stringify({
generation_id: '550e8400-e29b-41d4-a716-446655440000',
items: [
{ post_type: 'instagram-reel', media_ids: ['img-001-abcd-efgh'] },
{ post_type: 'tiktok-video', media_ids: ['img-001-abcd-efgh'] },
],
}),
})
const data = await res.json()import requests
res = requests.post(
"https://powerpost.ai/api/v1/posts",
headers={
"x-api-key": "pp_live_sk_YOUR_KEY",
"X-Workspace-Id": "YOUR_WORKSPACE_ID",
"Content-Type": "application/json",
},
json={
"generation_id": "550e8400-e29b-41d4-a716-446655440000",
"items": [
{"post_type": "instagram-reel", "media_ids": ["img-001-abcd-efgh"]},
{"post_type": "tiktok-video", "media_ids": ["img-001-abcd-efgh"]},
],
},
)
data = res.json()
Custom Content
Create a post with your own content — no generation needed:
curl -X POST https://powerpost.ai/api/v1/posts \
-H "x-api-key: pp_live_sk_YOUR_KEY" \
-H "X-Workspace-Id: YOUR_WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"post_type": "x-post",
"content": "Just shipped the biggest update of the year. Thread below."
},
{
"post_type": "instagram-feed",
"content": "Our biggest update of the year is live! Check link in bio for details.",
"media_ids": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"]
}
]
}'const res = await fetch('https://powerpost.ai/api/v1/posts', {
method: 'POST',
headers: {
'x-api-key': 'pp_live_sk_YOUR_KEY',
'X-Workspace-Id': 'YOUR_WORKSPACE_ID',
'Content-Type': 'application/json',
},
body: JSON.stringify({
items: [
{
post_type: 'x-post',
content: 'Just shipped the biggest update of the year. Thread below.',
},
{
post_type: 'instagram-feed',
content: 'Our biggest update of the year is live! Check link in bio for details.',
media_ids: ['a1b2c3d4-e5f6-7890-abcd-ef1234567890'],
},
],
}),
})
const data = await res.json()import requests
res = requests.post(
"https://powerpost.ai/api/v1/posts",
headers={
"x-api-key": "pp_live_sk_YOUR_KEY",
"X-Workspace-Id": "YOUR_WORKSPACE_ID",
"Content-Type": "application/json",
},
json={
"items": [
{
"post_type": "x-post",
"content": "Just shipped the biggest update of the year. Thread below.",
},
{
"post_type": "instagram-feed",
"content": "Our biggest update of the year is live! Check link in bio for details.",
"media_ids": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"],
},
],
},
)
data = res.json()
YouTube Post
YouTube requires a title in addition to the description:
curl -X POST https://powerpost.ai/api/v1/posts \
-H "x-api-key: pp_live_sk_YOUR_KEY" \
-H "X-Workspace-Id: YOUR_WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"post_type": "youtube-short",
"title": "We Just Shipped Dark Mode",
"content": "Dark mode is finally here across all our apps. #darkmode #tech",
"media_ids": ["vid-001-abcd-efgh"]
}
]
}'const res = await fetch('https://powerpost.ai/api/v1/posts', {
method: 'POST',
headers: {
'x-api-key': 'pp_live_sk_YOUR_KEY',
'X-Workspace-Id': 'YOUR_WORKSPACE_ID',
'Content-Type': 'application/json',
},
body: JSON.stringify({
items: [
{
post_type: 'youtube-short',
title: 'We Just Shipped Dark Mode',
content: 'Dark mode is finally here across all our apps. #darkmode #tech',
media_ids: ['vid-001-abcd-efgh'],
},
],
}),
})
const data = await res.json()import requests
res = requests.post(
"https://powerpost.ai/api/v1/posts",
headers={
"x-api-key": "pp_live_sk_YOUR_KEY",
"X-Workspace-Id": "YOUR_WORKSPACE_ID",
"Content-Type": "application/json",
},
json={
"items": [
{
"post_type": "youtube-short",
"title": "We Just Shipped Dark Mode",
"content": "Dark mode is finally here across all our apps. #darkmode #tech",
"media_ids": ["vid-001-abcd-efgh"],
},
],
},
)
data = res.json()
Response
{
"post_id": "post-550e8400-e29b-41d4-a716-446655440000",
"status": "draft",
"created_at": "2026-01-10T18:30:00Z",
"items": [
{
"item_id": "item-001",
"post_type": "instagram-reel",
"platform": "instagram",
"content": "Dark mode is here! Your late-night scrolling just got easier... #DarkMode",
"media_ids": ["img-001-abcd-efgh"],
"status": "draft"
},
{
"item_id": "item-002",
"post_type": "tiktok-video",
"platform": "tiktok",
"content": "POV: your eyes at 2am finally getting some relief... #darkmode #tech",
"media_ids": ["img-001-abcd-efgh"],
"status": "draft"
},
{
"item_id": "item-003",
"post_type": "youtube-short",
"platform": "youtube",
"title": "Dark mode launch",
"content": "We finally shipped dark mode...",
"media_ids": ["img-001-abcd-efgh"],
"status": "draft"
}
]
}title only appears on items where it was set on input (typically YouTube post types). Item
fields like platform_post_id, platform_url, posted_at, and error only appear after
publishing — see Get Post.
Get Post
GET /api/v1/posts/{id}
Retrieve the details and status of a post.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | The post ID |
Example
curl https://powerpost.ai/api/v1/posts/post-550e8400-e29b-41d4-a716-446655440000 \
-H "x-api-key: pp_live_sk_YOUR_KEY" \
-H "X-Workspace-Id: YOUR_WORKSPACE_ID"const res = await fetch(
'https://powerpost.ai/api/v1/posts/post-550e8400-e29b-41d4-a716-446655440000',
{
headers: {
'x-api-key': 'pp_live_sk_YOUR_KEY',
'X-Workspace-Id': 'YOUR_WORKSPACE_ID',
},
}
)
const data = await res.json()import requests
res = requests.get(
"https://powerpost.ai/api/v1/posts/post-550e8400-e29b-41d4-a716-446655440000",
headers={
"x-api-key": "pp_live_sk_YOUR_KEY",
"X-Workspace-Id": "YOUR_WORKSPACE_ID",
},
)
data = res.json()
Response
{
"post_id": "post-550e8400-e29b-41d4-a716-446655440000",
"status": "sent",
"created_at": "2026-01-10T18:30:00Z",
"items": [
{
"item_id": "item-001",
"post_type": "instagram-reel",
"platform": "instagram",
"content": "Dark mode is here!...",
"media_ids": ["img-001-abcd-efgh"],
"status": "posted",
"platform_post_id": "17898455678012345",
"platform_url": "https://instagram.com/p/CxYz123abc/",
"posted_at": "2026-01-10T18:35:02Z"
},
{
"item_id": "item-002",
"post_type": "tiktok-video",
"platform": "tiktok",
"content": "POV: your eyes at 2am...",
"media_ids": ["img-001-abcd-efgh"],
"status": "posted",
"platform_post_id": "7234567890123456789",
"platform_url": "https://www.tiktok.com/@user/video/7234567890123456789",
"posted_at": "2026-01-10T18:35:14Z"
},
{
"item_id": "item-003",
"post_type": "youtube-short",
"platform": "youtube",
"title": "Dark mode launch",
"content": "We finally shipped dark mode...",
"media_ids": ["img-001-abcd-efgh"],
"status": "failed",
"error": "Upload exceeded daily quota"
}
]
}Item fields are conditional. title is included for items that have one (typically YouTube
post types). platform_post_id, platform_url, and posted_at appear only after a successful
publish. error appears only when the item is in failed status.
Publish Post
POST /api/v1/posts/{id}/publish
Publish a draft post to connected social platforms. Each item is published to its target platform independently — if one fails, the others still go through.
Prerequisites
Before publishing, ensure:
- Platforms are connected — Connect your social accounts in Settings → Connections
- Post is publishable — A post can be published only when its status is
draft. After first publish starts, the post moves tosentand each item owns its own lifecycle. Retry failed items withPOST /api/v1/post-items/{id}/retry. - Sufficient credits — Publishing costs vary by platform (premium platforms like X cost more)
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | The post ID |
Body Parameters
| Field | Type | Required | Description |
|---|---|---|---|
items | array | No | Per-item publish settings. Each entry needs item_id; settings is optional. Items omitted from the array publish with defaults — see Publish Settings Reference. |
If items is omitted entirely (or sent without settings), all items publish with the documented defaults.
Example
curl -X POST https://powerpost.ai/api/v1/posts/post-550e8400-e29b-41d4-a716-446655440000/publish \
-H "x-api-key: pp_live_sk_YOUR_KEY" \
-H "X-Workspace-Id: YOUR_WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"item_id": "item-yt-002-cccc-dddd",
"settings": {
"privacy_status": "public",
"self_declared_made_for_kids": false,
"embeddable": true
}
}
]
}'const res = await fetch(
'https://powerpost.ai/api/v1/posts/post-550e8400-e29b-41d4-a716-446655440000/publish',
{
method: 'POST',
headers: {
'x-api-key': 'pp_live_sk_YOUR_KEY',
'X-Workspace-Id': 'YOUR_WORKSPACE_ID',
'Content-Type': 'application/json',
},
body: JSON.stringify({
items: [
{
item_id: 'item-yt-002-cccc-dddd',
settings: {
privacy_status: 'public',
self_declared_made_for_kids: false,
embeddable: true,
},
},
],
}),
}
)
const data = await res.json()import requests
res = requests.post(
"https://powerpost.ai/api/v1/posts/post-550e8400-e29b-41d4-a716-446655440000/publish",
headers={
"x-api-key": "pp_live_sk_YOUR_KEY",
"X-Workspace-Id": "YOUR_WORKSPACE_ID",
"Content-Type": "application/json",
},
json={
"items": [
{
"item_id": "item-yt-002-cccc-dddd",
"settings": {
"privacy_status": "public",
"self_declared_made_for_kids": False,
"embeddable": True,
},
}
]
},
)
data = res.json()
Response
{
"post_id": "post-550e8400-e29b-41d4-a716-446655440000",
"status": "draft",
"status_url": "/api/v1/posts/post-550e8400-e29b-41d4-a716-446655440000"
}| Field | Type | Description |
|---|---|---|
post_id | string | The post ID |
status | string | Stored post status at enqueue time |
status_url | string | Relative URL to poll for status |
Publishing is asynchronous. A 200 means the publish was accepted and enqueued — the post stays draft until the background job claims it and moves it to sent. Poll the Get Post endpoint or use webhooks to know when publishing completes.
Retry Post Item
POST /api/v1/post-items/{id}/retry
Retries one failed item from a sent post. The item moves back through posting and resolves to posted or failed.
Response
{
"post_item_id": "item-003",
"status": "posting",
"task_id": "run_123"
}Publishing Costs
Publishing costs vary by platform. Premium platforms (X) cost more due to higher API costs. Credits are charged per successful item in the background — the publish response itself does not include a cost figure since nothing has been charged yet. Check your balance via Get Credits after publishing completes. See the pricing page for current rates.
Credits are only charged for items that publish successfully. If an item fails, no credits are charged for it.
Schedule Post
POST /api/v1/posts/{id}/schedule
Schedule a draft post to publish automatically at a future time. The post is held in scheduled status until then, when it publishes exactly as Publish Post would.
Prerequisites
Same as publishing — platforms connected and content ready. The post must be in draft status. Credits aren't checked when you schedule; they're charged per successful item when the post publishes, so make sure the balance is funded before the scheduled time.
A workspace can hold up to 100 scheduled posts at once; scheduling more returns a 400.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | The post ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
scheduled_at | string | Yes | ISO 8601 datetime with a timezone offset, e.g. 2026-02-01T15:00:00Z. Must be at least a minute in the future and within 60 days. |
items | array | No | Optional per-item publish settings. Same shape as the Publish Post body — see Publish Settings Reference. |
Example
curl -X POST https://powerpost.ai/api/v1/posts/post-550e8400-e29b-41d4-a716-446655440000/schedule \
-H "x-api-key: pp_live_sk_YOUR_KEY" \
-H "X-Workspace-Id: YOUR_WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"scheduled_at": "2026-02-01T15:00:00Z",
"items": [
{
"item_id": "item-yt-002-cccc-dddd",
"settings": {
"privacy_status": "public",
"self_declared_made_for_kids": false,
"embeddable": true
}
}
]
}'const res = await fetch(
'https://powerpost.ai/api/v1/posts/post-550e8400-e29b-41d4-a716-446655440000/schedule',
{
method: 'POST',
headers: {
'x-api-key': 'pp_live_sk_YOUR_KEY',
'X-Workspace-Id': 'YOUR_WORKSPACE_ID',
'Content-Type': 'application/json',
},
body: JSON.stringify({
scheduled_at: '2026-02-01T15:00:00Z',
items: [
{
item_id: 'item-yt-002-cccc-dddd',
settings: {
privacy_status: 'public',
self_declared_made_for_kids: false,
embeddable: true,
},
},
],
}),
}
)
const data = await res.json()import requests
res = requests.post(
"https://powerpost.ai/api/v1/posts/post-550e8400-e29b-41d4-a716-446655440000/schedule",
headers={
"x-api-key": "pp_live_sk_YOUR_KEY",
"X-Workspace-Id": "YOUR_WORKSPACE_ID",
"Content-Type": "application/json",
},
json={
"scheduled_at": "2026-02-01T15:00:00Z",
"items": [
{
"item_id": "item-yt-002-cccc-dddd",
"settings": {
"privacy_status": "public",
"self_declared_made_for_kids": False,
"embeddable": True,
},
}
],
},
)
data = res.json()Response
{
"post_id": "post-550e8400-e29b-41d4-a716-446655440000",
"status": "scheduled",
"scheduled_at": "2026-02-01T15:00:00Z",
"status_url": "/api/v1/posts/post-550e8400-e29b-41d4-a716-446655440000"
}| Field | Type | Description |
|---|---|---|
post_id | string | The post ID |
status | string | scheduled while waiting to publish |
scheduled_at | string | The confirmed publish time |
status_url | string | Relative URL to poll for status |
Scheduling needs an API key with the posts:publish scope. A key without it gets a 403.
Publish Settings Reference
Some post types accept extra publish-time settings via the optional items[].settings field on Publish Post and Schedule Post.
If items is omitted, or an item appears without settings, TikTok posts publish as SELF_ONLY
(private to the creator) and YouTube posts publish as private. Set settings explicitly to publish
publicly.
TikTok Video (tiktok-video)
| Field | Type | Allowed values | Default |
|---|---|---|---|
privacy_level | string | PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY | SELF_ONLY |
disable_comment | boolean | false | |
disable_duet | boolean | false | |
disable_stitch | boolean | false | |
brand_content_toggle | boolean | false | |
brand_organic_toggle | boolean | false | |
is_brand_content | boolean | false |
TikTok Photos (tiktok-photos)
Same as TikTok Video minus disable_duet and disable_stitch.
YouTube Video & Short (youtube-video, youtube-short)
| Field | Type | Allowed values | Default |
|---|---|---|---|
privacy_status | string | public, unlisted, private | private |
self_declared_made_for_kids | boolean | false | |
embeddable | boolean | true |
Other Platforms
Instagram, Facebook, X, and LinkedIn have no publish-time settings. Sending settings for one of those items returns 400.
Cancel Scheduled Post
POST /api/v1/posts/{id}/cancel-publish
Cancel a scheduled (or in-progress) publish and revert the post to a draft. Only works while nothing has gone out yet — once any item has started posting, the publish can no longer be cancelled.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | The post ID |
Example
curl -X POST https://powerpost.ai/api/v1/posts/post-550e8400-e29b-41d4-a716-446655440000/cancel-publish \
-H "x-api-key: pp_live_sk_YOUR_KEY" \
-H "X-Workspace-Id: YOUR_WORKSPACE_ID"const res = await fetch(
'https://powerpost.ai/api/v1/posts/post-550e8400-e29b-41d4-a716-446655440000/cancel-publish',
{
method: 'POST',
headers: {
'x-api-key': 'pp_live_sk_YOUR_KEY',
'X-Workspace-Id': 'YOUR_WORKSPACE_ID',
},
}
)
const data = await res.json()import requests
res = requests.post(
"https://powerpost.ai/api/v1/posts/post-550e8400-e29b-41d4-a716-446655440000/cancel-publish",
headers={
"x-api-key": "pp_live_sk_YOUR_KEY",
"X-Workspace-Id": "YOUR_WORKSPACE_ID",
},
)
data = res.json()Response
{
"post_id": "post-550e8400-e29b-41d4-a716-446655440000",
"status": "draft",
"reverted": true
}| Field | Type | Description |
|---|---|---|
post_id | string | The post ID |
status | string | draft — the post is back to a draft |
reverted | boolean | Always true on a successful response. A post that has already started publishing returns a 400 instead |
Cancelling needs an API key with the posts:publish scope. A key without it gets a 403.
Post Status Values
Post Status
| Status | Description |
|---|---|
draft | Created, not yet published |
scheduled | Waiting on a delayed publish job to fire |
sent | First publish has started; items own outcomes |
Item Status
| Status | Description |
|---|---|
draft | Not yet published |
posting | Publishing in progress |
posted | Successfully published |
failed | Failed (see error field) |
Post Types Reference
| Platform | Post Types |
|---|---|
instagram-feed, instagram-reel, instagram-story | |
| TikTok | tiktok-video, tiktok-photos |
| YouTube | youtube-video, youtube-short |
| X | x-post |
facebook-post, facebook-reel, facebook-story | |
linkedin-post |
Errors
| Code | Description |
|---|---|
| 400 | Invalid request body, bad scheduled_at, or bad items[].settings |
| 401 | Invalid API key |
| 402 | Insufficient credits for publishing |
| 403 | API key is missing the posts:publish scope |
| 404 | Post not found or generation not found |
| 409 | Post is not in a publishable state (already published, or already scheduled) |
| 422 | Platform not connected for one or more post types |
| 429 | Rate limit exceeded |
| 502 | Scheduled publish could not be cancelled; try again shortly |