PostWing

Agent API

PostWing API docs

Schedule, edit, and manage social posts from agents and apps. Same capabilities as the dashboard: accounts, workspaces, media, platform options, and a required preview → confirm write flow.

What the API offers

Everything an AI agent or backend needs to run PostWing without the dashboard UI.

Schedule posts

Preview a caption, time, media, and targets, then confirm to put the post on the calendar (same as the dashboard).

Edit and cancel

Change caption, time, media, or targets on up to 20 scheduled posts at once, or cancel with explicit confirmation.

Connect accounts

Start OAuth for Instagram, TikTok, YouTube, LinkedIn, X, Facebook, Pinterest, or Threads, then sync into the workspace.

Workspaces

List, create (Scale), and switch the active workspace so every call hits the right accounts and posts.

Upload media

Get a direct upload URL, PUT the file to storage, then pass the returned media_url when you schedule.

MCP for agents

Same capabilities over Model Context Protocol for Claude, ChatGPT, Cursor, and other MCP clients.

Base URLs

  • REST: https://postwing.io/api/v1
  • OpenAPI: https://postwing.io/api/v1/openapi.json
  • MCP: https://postwing.io/api/mcp

Quickstart

Four steps from zero to a scheduled post.

  1. 1Create an API key in Dashboard → AI & API (or connect via OAuth).
  2. 2GET /accounts to see connected accounts and your timezone.
  3. 3Upload media with POST /media/upload-url, then PUT the file to upload_url (optional for text posts).
  4. 4POST /posts/preview with caption, scheduled_at, targets, and media_items, then confirm.

Authentication

Send a Bearer token on every protected request. Prefer OAuth for third-party agents; API keys for scripts and servers.

MethodHow
API keyCreate a key in Dashboard → AI & API. Use Authorization: Bearer pw_live_…
OAuth (PKCE)Resource https://postwing.io/api/v1. Authorize at /oauth/authorize, tokens at /oauth/token. Dynamic client registration is supported for trusted hosts including muse.ai and cursor.com.
Header
Authorization: Bearer pw_live_…
Content-Type: application/json

Scopes

Request only what your client needs.

ScopeAccess
accounts:readList connected accounts and Pinterest boards
accounts:writeConnect, sync, and disconnect social accounts
posts:readList posts, get a post, schedule summary
posts:writeUpload media, preview, confirm, edit, and cancel
workspaces:readList workspaces
workspaces:writeCreate workspaces and switch the active one

Write safety (required)

Scheduling and edits never run in one shot. Preview first, review, then confirm.

  1. POST /posts/preview (or /posts/edit/preview) with the intended change
  2. Review caption, accounts, media, time, and platform options
  3. POST /posts/confirm (or /posts/edit/confirm) with confirmation_token
  • Always call preview before confirm. Never invent a confirmation token.
  • Show the preview to the user (or your operator) and wait for explicit approval before confirm.
  • Confirmation tokens expire in 5 minutes and are bound to that user.
  • Cancel or disconnect only after the user agrees (confirm: true).
  • Only operate on accounts and posts returned by the API for the authenticated user.
  • Use the user’s PostWing timezone for relative times like “tomorrow at 9am”.

Errors & rate limits

Errors use a simple JSON shape: { "error": "message" }.

StatusMeaning
401Missing or invalid Bearer token
403Missing scope, inactive subscription, plan limit, or Scale-only feature
400Validation or business rule error (message in error)
404Account, post, or board not found for this user/workspace
429Rate limit exceeded (reads ~60/min, writes ~10/min, media upload ~20/min)
500Unexpected server error
  • Reads: about 60 requests/min per credential
  • Writes: about 10 requests/min per credential
  • Media upload: about 20 requests/min per user

Media upload

Upload files before you schedule. Get an upload URL, PUT the file directly to storage, then pass media_url into preview.

  1. POST /media/upload-url with filename, content_type, and size
  2. PUT the raw file bytes to upload_url (same Content-Type)
  3. Pass media_url into POST /posts/preview as media_items[].url

You can also pass a durable public HTTPS URL straight into preview. POST /media (URL import) remains available but is optional.

media_items fields

FieldTypeRequiredDescription
urlstring (uri)YesPublic HTTPS URL of the image or video.
type"image" | "video"YesMedia kind. Inferred from the URL extension if omitted.
thumbnail_urlstring (uri)NoVideo only. Custom cover image URL (JPG/PNG/WEBP). Takes precedence over thumbnail_timestamp_ms. Same capability as Custom thumbnail in the dashboard.
thumbnail_timestamp_msinteger ≥ 0NoVideo only. Milliseconds into the video to use as the cover frame when thumbnail_url is omitted. Default: 1000 (first second). Ignored when thumbnail_url is set.

Video cover rules

  • If thumbnail_url is set, that image is the cover (Instagram Reel cover and other platforms that accept a custom thumbnail).
  • Otherwise PostWing uses thumbnail_timestamp_ms (default 1000) to pick a frame from the video.
  • Cover images: JPG, PNG, WEBP, max 10MB. Must be a public HTTPS URL.
  • thumbnail_url always wins when present.

Formats & size

KindFormatsLimits
ImagesJPG, PNG, WEBP10MB each · Up to 35 images per post (platform limits still apply when publishing)
VideosMP4, MOV, WEBM450MB (URL import via Agent API may be lower; see upload_media errors) · One video per post
Video coversJPG, PNG, WEBP10MB

Per-platform image caps

Extra images beyond a platform’s cap are omitted for that platform when publishing.

PlatformMax images
Instagram10
Facebook20
LinkedIn20
X4
Threads4
TikTok30
Pinterest1
YouTubevideo only

Captions

One global caption, optional per-account overrides. Same model as the dashboard.

FieldBehavior
captionGlobal caption for every target. Required on schedule preview. Pass "" or " " for no caption (both mean empty).
targets[].captionWhen set (including "" or " "), overrides the global caption for that account only. Omit the field to inherit the global caption.
targets[].platform_options.captionLegacy/alternate place for a per-account caption. Prefer targets[].caption.

Platform options

Pass these on targets[].platform_options when scheduling or editing. Same fields as the dashboard compose form.

X (Twitter)

x

FieldTypeDescription
quote_tweet_urlstringTweet URL or status ID to quote. Creates a native X quote post (not a plain link in the caption). Example: https://x.com/user/status/123
reply_settingsstring | nullWho can reply: null (everyone), following, mentionedUsers, subscribers, verified

Instagram

instagram

FieldTypeDescription
placementstringSet automatically by PostWing: reels for video, timeline for image/text. You usually do not need to send this.
share_to_feedbooleanVideo/Reels only. If true, also share the Reel to the main feed (default true). Same as “Share Reels to feed” in the dashboard.
trial_reel_typestring | nullVideo only. null (off), manual, or performance. Trial reels require 1000+ followers; max 5 per day.

Facebook

facebook

FieldTypeDescription
placementstringtimeline or reels. Video defaults to reels; images/text use timeline.

YouTube

youtube

FieldTypeDescription
titlestringRequired for YouTube. Shorts/long-form title.
made_for_kidsbooleanWhether the video is made for kids (default false)

TikTok

tiktok

FieldTypeDescription
titlestringImage posts only (not used for video)
privacy_statusstringpublic or private (default public)
allow_commentbooleanAllow comments (default true)
allow_duetbooleanAllow duets (default true)
allow_stitchbooleanAllow stitch (default true)
is_ai_generatedbooleanMark as AI-generated content (default false)
is_draftbooleanSave as draft in the TikTok app (default false)

Pinterest

pinterest

FieldTypeDescription
board_idsstring[]Required. Board IDs from GET /accounts/pinterest-boards or list_pinterest_boards
titlestringPin title
linkstringDestination URL for the pin

LinkedIn

linkedin

FieldTypeDescription
(none)n/aCaption only; no extra platform options

Threads

threads

FieldTypeDescription
(none)n/aCaption only; no extra platform options

Discovery

Public metadata. No auth required.

GET/api/v1public

API discovery

Returns base URL, OpenAPI link, docs links, auth hints, and available scopes.

Hit this first when wiring a new client. It tells you where OpenAPI lives, which scopes exist, and that writes use preview → confirm.

Request
curl -s "https://postwing.io/api/v1"
Response (200)
{
  "name": "PostWing Agent API",
  "version": "1.0.0",
  "base_url": "https://postwing.io/api/v1",
  "openapi_url": "https://postwing.io/api/v1/openapi.json",
  "documentation": "https://postwing.io/docs",
  "security": {
    "scopes": [
      "accounts:read",
      "accounts:write",
      "posts:read",
      "posts:write",
      "workspaces:read",
      "workspaces:write"
    ],
    "write_flow": "preview → confirmation_token → confirm (token expires in 5 minutes)",
    "isolation": "All data is scoped to the authenticated user and active workspace"
  }
}
GET/api/v1/openapi.jsonpublic

OpenAPI document

Machine-readable OpenAPI 3.1 schema for the full Agent API.

Use this URL in Muse custom connectors, code generators, or any OpenAPI-aware tool. Prefer OAuth when the host asks how to authenticate.

Request
curl -s "https://postwing.io/api/v1/openapi.json"
Response (200)
Full OpenAPI 3.1 object (paths, schemas, security schemes). Too large to inline here.

Accounts

Connect, list, sync, and disconnect social accounts in the active workspace.

GET/api/v1/accountsaccounts:read

List connected accounts

Returns every social account in the active workspace plus the user timezone.

Start here before scheduling. Use account_id values in targets[], and use timezone when interpreting relative times like “tomorrow at 9am”.

  • status is connected or reauth_required.
  • Only accounts in the active workspace are returned.
Request
curl -s "https://postwing.io/api/v1/accounts" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "timezone": "America/New_York",
  "accounts": [
    {
      "account_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "platform": "instagram",
      "handle": "@brand",
      "display_name": "Brand",
      "status": "connected"
    },
    {
      "account_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "platform": "linkedin",
      "handle": "company-page",
      "display_name": "Company",
      "status": "connected"
    }
  ]
}
POST/api/v1/accountsaccounts:write

Start account connect

Starts OAuth for a platform and returns a browser URL the user must open.

This does not finish the connection by itself. After the user authorizes in the browser, call POST /accounts/sync to import the account into PostWing.

Request body

NameTypeRequiredDescription
platformstringYesOne of: facebook, instagram, linkedin, pinterest, threads, tiktok, x, youtube
  • Account limits follow the user’s plan.
  • Show the URL to the user; do not invent a completed connection.
Request
curl -s -X POST "https://postwing.io/api/v1/accounts" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "platform": "linkedin"
}'
Response (200)
{
  "url": "https://…oauth…",
  "platform": "linkedin",
  "next_step": "Open this URL in the browser so the user can authorize the social account. After they finish, call POST /accounts/sync."
}
POST/api/v1/accounts/syncaccounts:write

Sync accounts after OAuth

Imports or refreshes accounts after the user finishes the connect browser flow.

Call this right after the user returns from the OAuth URL. No body is required. Returns the accounts now available in the active workspace.

Request
curl -s -X POST "https://postwing.io/api/v1/accounts/sync" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "synced_count": 2,
  "accounts": [
    {
      "account_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "platform": "linkedin",
      "handle": "company-page",
      "display_name": "Company",
      "status": "connected"
    }
  ]
}
DELETE/api/v1/accountsaccounts:write

Disconnect an account

Removes a social account from the workspace after explicit confirmation.

Destructive. Only call after the user agrees. confirm must be true or the API rejects the request.

Request body

NameTypeRequiredDescription
account_idstring (uuid)YesPostWing social account UUID from GET /accounts
confirmbooleanYesMust be true
Request
curl -s -X DELETE "https://postwing.io/api/v1/accounts" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "account_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "confirm": true
}'
Response (200)
{
  "disconnected": true,
  "account_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "platform": "instagram",
  "handle": "@brand"
}
GET/api/v1/accounts/pinterest-boardsaccounts:read

List Pinterest boards

Lists boards for a connected Pinterest account (needed before scheduling Pins).

Pinterest posts require board_ids in targets[].platform_options. Fetch boards with this endpoint first, then pass the IDs into preview.

Query parameters

NameTypeRequiredDescription
account_idstring (uuid)YesPinterest account UUID from GET /accounts
Request
curl -s "https://postwing.io/api/v1/accounts/pinterest-boards?account_id=a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "account_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "boards": [
    {
      "id": "1234567890",
      "name": "Marketing"
    }
  ]
}

Workspaces

Everything you schedule or connect is scoped to the active workspace.

GET/api/v1/workspacesworkspaces:read

List workspaces

Returns workspaces the user belongs to and which one is active.

Use this to show workspace context, or before switching with POST /workspaces/active.

Request
curl -s "https://postwing.io/api/v1/workspaces" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "active_workspace_id": "ws-uuid-1",
  "workspaces": [
    {
      "workspace_id": "ws-uuid-1",
      "name": "Default",
      "created_at": "2025-01-15T12:00:00.000Z",
      "role": "owner",
      "is_active": true
    }
  ]
}
POST/api/v1/workspacesworkspaces:write

Create a workspace

Creates a workspace (Scale plan).

Useful for agencies and multi-brand setups. Returns 403 if the plan does not include workspaces.

Request body

NameTypeRequiredDescription
namestringYesNon-empty workspace name
Request
curl -s -X POST "https://postwing.io/api/v1/workspaces" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Client A"
}'
Response (200)
{
  "workspace": {
    "workspace_id": "ws-uuid-2",
    "name": "Client A",
    "created_at": "2026-10-04T14:00:00.000Z"
  }
}
POST/api/v1/workspaces/activeworkspaces:write

Switch active workspace

Sets which workspace subsequent account and post calls use.

After switching, GET /accounts and GET /posts reflect the new workspace until you switch again.

Request body

NameTypeRequiredDescription
workspace_idstring (uuid)YesWorkspace UUID from GET /workspaces (workspaceId alias accepted)
Request
curl -s -X POST "https://postwing.io/api/v1/workspaces/active" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "workspace_id": "ws-uuid-2"
}'
Response (200)
{
  "active_workspace": {
    "workspace_id": "ws-uuid-2",
    "name": "Client A",
    "created_at": "2026-10-04T14:00:00.000Z"
  },
  "next_step": "Subsequent account and post calls use this workspace until switched again."
}

Media

Upload files first, then pass the returned media_url into schedule preview.

POST/api/v1/media/upload-urlposts:write

Get a direct upload URL

Returns upload_url and media_url. PUT your file to upload_url, then use media_url in preview.

Preferred path for images and videos from your app or script. Same flow as the dashboard: prepare an upload URL, send the file bytes with PUT, then schedule with the returned media_url.

Request body

NameTypeRequiredDescription
filenamestringYesOriginal filename (e.g. launch.jpg)
content_typestringYesMIME type. contentType alias accepted. e.g. image/jpeg, video/mp4
sizenumberYesFile size in bytes
  • After this call, PUT the raw file to upload_url (not JSON). Include Content-Type matching content_type.
  • Then pass { url: media_url, type } in media_items on POST /posts/preview.
  • Images: JPG, PNG, WEBP up to 10MB. Videos: MP4, MOV, WEBM up to 450MB.
  • Rate limited (~20 media requests/min per user).
Request
curl -s -X POST "https://postwing.io/api/v1/media/upload-url" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "filename": "launch.jpg",
  "content_type": "image/jpeg",
  "size": 245760
}'
Response (200)
{
  "upload_url": "https://upload…/…",
  "media_url": "https://cdn…/launch.jpg",
  "type": "image",
  "next_step": "PUT the file bytes to upload_url with the same Content-Type. Then pass media_url into POST /posts/preview as media_items[].url."
}
POST/api/v1/mediaposts:write

Import media from URLs (optional)

Optional. Downloads public HTTPS media and returns media_items. Prefer /media/upload-url for local files.

Kept for existing clients. Prefer POST /media/upload-url for files on disk, or pass a durable public URL directly into POST /posts/preview as media_items / media_urls. URL import is capped at 100MB for videos.

Request body

NameTypeRequiredDescription
urlsstring[] (HTTPS URIs)Yes1–10 public URLs. Only one video URL per video post.
  • Optional. Most clients should use /media/upload-url instead.
  • Private or signed URLs that expire often fail.
Request
curl -s -X POST "https://postwing.io/api/v1/media" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "urls": [
    "https://cdn.example.com/launch.jpg"
  ]
}'
Response (200)
{
  "media_items": [
    {
      "url": "https://cdn.postwing…/launch.jpg",
      "type": "image"
    }
  ],
  "upload_page": "https://postwing.io/dashboard/ai/upload",
  "next_step": "Pass media_items into POST /posts/preview along with caption, schedule time, and targets."
}

Posts

List, inspect, schedule, edit, and cancel posts. Schedule and edit always use preview → confirm.

GET/api/v1/postsposts:read

List scheduled posts

Paginated list of posts in the active workspace.

By default only upcoming posts (scheduled_at ≥ now) are returned. This is one page, not the full calendar. Follow has_more with a higher offset, or use GET /schedule/summary for aggregate counts.

Query parameters

NameTypeRequiredDescription
limitintegerNoPage size. Default 20 (full) or 100 (summary). Max 50 full / 200 summary.
offsetintegerNoPagination offset (default 0)
fields"full" | "summary"Nosummary returns compact rows (no captions). full includes richer post rows.
statusstringNoscheduled | publishing | published | partially_failed | failed | paused
fromISO datetimeNoscheduled_at ≥ from
untilISO datetimeNoscheduled_at ≤ until
include_pastbooleanNoIf true (and from omitted), include past posts
Request
curl -s "https://postwing.io/api/v1/posts?limit=20&offset=0&fields=full" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "total": 42,
  "returned": 20,
  "offset": 0,
  "has_more": true,
  "pagination_note": "Showing posts 1-20 of 42. This is NOT the full list. Call again with offset=20, or use fields=summary.",
  "posts": [
    {
      "id": "post-uuid",
      "caption_common": "Hello world",
      "scheduled_at": "2026-10-05T15:00:00.000Z",
      "status": "scheduled",
      "created_at": "2026-10-04T10:00:00.000Z",
      "post_targets": [
        {
          "platform": "linkedin",
          "account_handle": "company",
          "status": "scheduled"
        }
      ]
    }
  ]
}
GET/api/v1/posts/{id}posts:read

Get one post

Full detail for a single post: media, per-account captions, and platform_options.

Use this when you need exact captions or platform settings before editing. Includes view_url to open the post in the dashboard.

Path parameters

NameTypeRequiredDescription
idstring (uuid)YesPost ID from list or confirm responses
Request
curl -s "https://postwing.io/api/v1/posts/post-uuid" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "timezone": "America/New_York",
  "post": {
    "id": "post-uuid",
    "caption_common": "Launch day",
    "scheduled_at": "2026-10-05T15:00:00.000Z",
    "scheduled_at_local": "10/5/2026, 11:00:00 AM",
    "status": "scheduled",
    "post_type": "image",
    "media": [
      {
        "url": "https://cdn.example.com/photo.jpg",
        "type": "image"
      }
    ],
    "targets": [
      {
        "account_id": "acct-uuid",
        "platform": "instagram",
        "handle": "@brand",
        "status": "scheduled",
        "caption": null,
        "platform_options": {
          "placement": "timeline"
        }
      }
    ],
    "view_url": "https://postwing.io/dashboard/post/post-uuid"
  }
}
POST/api/v1/posts/previewposts:write

Preview a schedule

Validates a post and returns a preview plus a confirmation_token (5 minute TTL).

This is the first step of every schedule. Nothing is published until you call confirm with the token after human approval. Prefer targets[] when you need per-account captions or platform_options.

Request body

NameTypeRequiredDescription
captionstringYesGlobal caption. Use "" or " " for no caption (both mean empty).
scheduled_atISO 8601 datetimeYesMust be in the future
targetsobject[]NoPreferred. Each item: { account_id, caption?, platform_options? }. Use instead of account_ids/platforms when you need per-account settings.
account_idsstring[] (uuid)NoPostWing account UUIDs from GET /accounts
platformsstring[]Noe.g. ["instagram","linkedin"] when one connected account per platform is enough
media_itemsMediaItem[]NoPreferred media. { url, type, thumbnail_url?, thumbnail_timestamp_ms? }
media_urlsstring[]NoShorthand public HTTPS URLs (imported/validated like media_items)
post_type"text" | "image" | "video"NoUsually inferred from media
  • You must provide at least one of targets, account_ids, or platforms.
  • Never invent a confirmation_token. Always take it from this response.
  • Tokens expire in 300 seconds and are bound to the authenticated user.
Request
curl -s -X POST "https://postwing.io/api/v1/posts/preview" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "caption": "Product update",
  "scheduled_at": "2026-10-05T18:00:00.000Z",
  "platforms": [
    "instagram",
    "linkedin"
  ],
  "media_items": [
    {
      "url": "https://cdn.example.com/photo.jpg",
      "type": "image"
    }
  ]
}'
Response (200)
{
  "preview": {
    "caption": "Product update",
    "scheduled_at": "2026-10-05T18:00:00.000Z",
    "scheduled_at_local": "10/5/2026, 2:00:00 PM",
    "timezone": "America/New_York",
    "post_type": "image",
    "targets": [
      {
        "account_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "platform": "instagram",
        "handle": "@brand",
        "caption": null,
        "platform_options": {}
      }
    ],
    "media_count": 1,
    "media": [
      {
        "url": "https://cdn.example.com/photo.jpg",
        "type": "image"
      }
    ]
  },
  "confirmation_token": "eyJ…signature",
  "expires_in_seconds": 300,
  "next_step": "Show the preview to the user. After explicit approval, POST /posts/confirm with confirmation_token."
}
POST/api/v1/posts/confirmposts:write

Confirm a schedule

Creates the scheduled post using the token from preview.

Call only after the operator approves the preview. The post appears on the PostWing calendar in the active workspace, same as the dashboard.

Request body

NameTypeRequiredDescription
confirmation_tokenstringYesToken from POST /posts/preview
Request
curl -s -X POST "https://postwing.io/api/v1/posts/confirm" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "confirmation_token": "eyJ…signature"
}'
Response (200)
{
  "scheduled": true,
  "post": {
    "id": "new-post-uuid",
    "caption_common": "Product update",
    "scheduled_at": "2026-10-05T18:00:00.000Z",
    "status": "scheduled",
    "targets": [
      {
        "platform": "instagram",
        "handle": "@brand"
      }
    ],
    "dashboard_url": "/dashboard/post/new-post-uuid"
  },
  "view_url": "https://postwing.io/dashboard/post/new-post-uuid"
}
POST/api/v1/posts/edit/previewposts:write

Preview edits

Dry-run changes on 1–20 scheduled posts; returns a diff and confirmation_token.

Only send fields you want to change. Everything else stays as-is. For batch time moves, use shift_minutes (absolute scheduled_at is not allowed across multiple posts).

Request body

NameTypeRequiredDescription
post_idsstring[] (uuid)Yes1–20 post IDs
captionstringNoNew global caption. "" or " " clears caption. Omit to keep current.
scheduled_atISO datetimeNoAbsolute new time (single post only)
shift_minutesintegerNoRelative shift for one or many posts
targetsobject[]NoReplace the full target list (captions / platform_options)
target_optionsobject[]NoPatch captions/platform_options for accounts already on the post without replacing targets
account_ids / platformsstring[]NoReplace targets by account IDs or platform names
media_items / media_urlsarrayNoReplace media
remove_mediabooleanNoClear all media when true
Request
curl -s -X POST "https://postwing.io/api/v1/posts/edit/preview" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "post_ids": [
    "post-uuid-1"
  ],
  "caption": "Updated copy",
  "shift_minutes": 30
}'
Response (200)
{
  "preview": {
    "post_count": 1,
    "timezone": "America/New_York",
    "requested_changes": {
      "caption": "Updated copy",
      "shift_minutes": 30
    },
    "posts": [
      {
        "post_id": "post-uuid-1",
        "changes": [
          "caption",
          "scheduled_at"
        ],
        "current": {
          "caption": "Old copy",
          "scheduled_at": "2026-10-05T15:00:00.000Z"
        },
        "updated": {
          "caption": "Updated copy",
          "scheduled_at": "2026-10-05T15:30:00.000Z"
        }
      }
    ]
  },
  "confirmation_token": "eyJ…signature",
  "expires_in_seconds": 300,
  "next_step": "Show current vs updated values. After approval, POST /posts/edit/confirm with confirmation_token."
}
POST/api/v1/posts/edit/confirmposts:write

Confirm edits

Applies the edit token from preview_edit.

If a post changed since preview (fingerprint mismatch), that post is rejected. Batch responses may include a failed array for partial success.

Request body

NameTypeRequiredDescription
confirmation_tokenstringYesToken from POST /posts/edit/preview
Request
curl -s -X POST "https://postwing.io/api/v1/posts/edit/confirm" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "confirmation_token": "eyJ…signature"
}'
Response (200)
{
  "updated": true,
  "updated_count": 1,
  "posts": [
    {
      "id": "post-uuid-1",
      "caption_common": "Updated copy",
      "scheduled_at": "2026-10-05T15:30:00.000Z",
      "status": "scheduled",
      "view_url": "https://postwing.io/dashboard/post/post-uuid-1"
    }
  ]
}
DELETE/api/v1/posts/{id}posts:write

Cancel a post

Cancels a scheduled or paused post after confirm: true.

Only scheduled or paused posts can be cancelled. Requires an explicit confirm: true body after the user agrees.

Path parameters

NameTypeRequiredDescription
idstring (uuid)YesPost ID to cancel

Request body

NameTypeRequiredDescription
confirmbooleanYesMust be true
Request
curl -s -X DELETE "https://postwing.io/api/v1/posts/post-uuid" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "confirm": true
}'
Response (200)
{
  "cancelled": true,
  "post_id": "post-uuid"
}

Schedule insights

Aggregate view of what is already on the calendar.

GET/api/v1/schedule/summaryposts:read

Schedule summary

Counts and patterns across upcoming posts in the active workspace.

Use this instead of paging through every post when you only need totals by platform, account, status, or day. Scans up to 1000 upcoming posts.

  • total_posts counts posts. by_platform / by_account count publications (a post to 3 accounts counts 3 times there).
Request
curl -s "https://postwing.io/api/v1/schedule/summary" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "timezone": "America/New_York",
  "total_posts": 15,
  "truncated": false,
  "range": {
    "first": "2026-10-05T09:00:00.000Z",
    "last": "2026-10-20T17:00:00.000Z"
  },
  "by_platform": {
    "instagram": 10,
    "linkedin": 8
  },
  "by_status": {
    "scheduled": 15
  },
  "by_account": [
    {
      "platform": "instagram",
      "handle": "@brand",
      "post_count": 6,
      "active_days": 3
    }
  ],
  "by_day": {
    "2026-10-05": {
      "total": 2,
      "platforms": {
        "instagram": 1,
        "linkedin": 1
      }
    }
  }
}

Workflow examples

End-to-end patterns you can copy. Always preview, then confirm.

Text post to Instagram + LinkedIn

1. Preview

curl -s -X POST "https://postwing.io/api/v1/posts/preview" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "caption": "Hello from the Agent API",
  "scheduled_at": "2026-10-05T09:00:00Z",
  "platforms": [
    "instagram",
    "linkedin"
  ]
}'

2. Confirm after approval

curl -s -X POST "https://postwing.io/api/v1/posts/confirm" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "confirmation_token": "…"
}'

Upload a video, then schedule a Reel with cover

1. Get upload URL

curl -s -X POST "https://postwing.io/api/v1/media/upload-url" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "filename": "reel.mp4",
  "content_type": "video/mp4",
  "size": 12582912
}'

2. PUT the file bytes to upload_url

curl -s -X PUT "<upload_url from step 1>" \
  -H "Content-Type: video/mp4" \
  --data-binary @reel.mp4

3. Preview with the returned media_url

curl -s -X POST "https://postwing.io/api/v1/posts/preview" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "caption": "",
  "scheduled_at": "2026-10-05T18:00:00Z",
  "targets": [
    {
      "account_id": "00000000-0000-0000-0000-000000000002",
      "platform_options": {
        "share_to_feed": true
      }
    }
  ],
  "media_items": [
    {
      "url": "https://cdn…/reel.mp4",
      "type": "video",
      "thumbnail_url": "https://cdn…/cover.jpg"
    }
  ]
}'

X native quote post

Preview with quote_tweet_url

curl -s -X POST "https://postwing.io/api/v1/posts/preview" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "caption": "Adding my take",
  "scheduled_at": "2026-10-05T15:00:00Z",
  "targets": [
    {
      "account_id": "00000000-0000-0000-0000-000000000001",
      "platform_options": {
        "quote_tweet_url": "https://x.com/someone/status/1234567890"
      }
    }
  ]
}'

Pinterest pin (boards required)

1. List boards

curl -s "https://postwing.io/api/v1/accounts/pinterest-boards?account_id=pinterest-account-uuid" \
  -H "Authorization: Bearer pw_live_…"

2. Preview pin

curl -s -X POST "https://postwing.io/api/v1/posts/preview" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "caption": "New lookbook",
  "scheduled_at": "2026-10-06T12:00:00Z",
  "targets": [
    {
      "account_id": "pinterest-account-uuid",
      "platform_options": {
        "board_ids": [
          "1234567890"
        ],
        "title": "Lookbook",
        "link": "https://example.com/shop"
      }
    }
  ],
  "media_items": [
    {
      "url": "https://cdn.example.com/pin.jpg",
      "type": "image"
    }
  ]
}'

MCP tools

Claude, ChatGPT, and Cursor connect over MCP at https://postwing.io/api/mcp. Same auth scopes, media fields, platform options, and preview → confirm rules as REST.

ToolScopeSummary
list_connected_accountsaccounts:readList accounts in the active workspace
connect_accountaccounts:writeStart OAuth connect; returns browser URL
sync_accountsaccounts:writeImport accounts after the user finishes OAuth
disconnect_accountaccounts:writeDisconnect an account (user_confirmed: true)
list_pinterest_boardsaccounts:readList boards for a Pinterest account
list_workspacesworkspaces:readList workspaces and the active one
create_workspaceworkspaces:writeCreate a workspace (Scale plan)
switch_workspaceworkspaces:writeSet the active workspace
list_scheduled_postsposts:readPaginated list of posts
get_scheduled_postposts:readFull detail for one post
get_schedule_summaryposts:readCounts and patterns across posts
preview_schedule_postposts:writePreview schedule; returns confirmation_token
confirm_schedule_postposts:writeConfirm schedule after user approval
preview_edit_postsposts:writePreview edits for up to 20 posts
confirm_edit_postsposts:writeConfirm edits after user approval
cancel_scheduled_postposts:writeCancel a post (user_confirmed: true)
request_media_uploadposts:writeOpen in-chat media upload widget
upload_mediaposts:writeImport media from public HTTPS URLs (optional; prefer upload widget)

Access requirements

  • Active PostWing subscription (7-day free trial available)
  • Connected social accounts in the user’s workspace (or connect via the API)
  • API key or OAuth token with the scopes you need
  • Platforms: Instagram, TikTok, YouTube, LinkedIn, X, Facebook, Pinterest, Threads
  • Confirmation tokens: 5 minute TTL, single-user bound
  • Batch edit: up to 20 posts per edit preview

Prefer chat?

Connect Claude or Muse to PostWing and schedule with a preview you approve before anything goes live.