PostWing

API · v2

PostWing API docs

Schedule and manage social posts from your app, scripts, or AI agents. One call to schedule, one Post object everywhere, and errors that tell you exactly what to fix.

Built on v1? The v1 docs are still available and v1 keeps working. See Migrating from v1.

What the API offers

Schedule in one call

POST /posts with a caption, a time, the accounts to post to, and optional media. It goes on your calendar like a dashboard post.

One Post object everywhere

Create, get, list, and edit all return the same shape, including each account’s status, published link, and error.

Clear errors

Every error has a stable code, a readable message, and the exact field at fault, like accounts[1].options.title.

Workspaces for agencies

Pass workspace_id to read or post in any workspace you belong to. Posts follow the workspace their accounts belong to.

Media and covers

Upload a file or pass a public HTTPS URL. Videos take a cover image or a frame time.

Platform options

Plain-language settings per account: Reels or feed, TikTok visibility, YouTube titles, Pinterest boards, X quote posts, and more.

Base URLs

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

Quickstart

From zero to a scheduled post.

  1. 1Create an API key in Dashboard → AI & API (or connect with OAuth).
  2. 2GET /accounts and copy the id of each account you want to post to.
  3. 3Optional: POST /media/uploads, then PUT the file to upload_url.
  4. 4POST /posts with caption, scheduled_at, accounts, and media. Done.
Schedule a post
curl -s -X POST "https://postwing.io/api/v2/posts" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "caption": "Launch day 🚀",
  "scheduled_at": "2026-10-20T09:00:00Z",
  "accounts": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    },
    {
      "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "caption": "Launch day. Link in bio.",
      "options": {
        "who_can_reply": "following"
      }
    }
  ],
  "media": [
    {
      "url": "https://cdn.example.com/launch.jpg"
    }
  ]
}'

Authentication

Send a Bearer token on every request. API keys for your own scripts and servers; OAuth for apps that act for other PostWing users.

MethodHow
API keyCreate a key in Dashboard → AI & API and send Authorization: Bearer pw_live_…
OAuth (PKCE)Resource https://postwing.io/api/v2. Authorize at /oauth/authorize, exchange tokens at /oauth/token. Dynamic client registration is supported.

Scopes

ScopeAllows
accounts:readList accounts and Pinterest boards
accounts:writeConnect, sync, and disconnect accounts
posts:readList posts, get a post, summary
posts:writeUpload media, schedule, edit, and cancel posts
workspaces:readList workspaces
workspaces:writeCreate workspaces and set the active one

The Post object

Create, get, list, and edit all return this shape.

Post
{
  "id": "7c1d2a8e-4b0f-4f8e-9a51-2f7f6b1c9d10",
  "workspace_id": "3f2a9c1e-8d4b-4e6a-b2c7-1a9e5d3f7b20",
  "status": "scheduled",
  "caption": "Launch day 🚀",
  "scheduled_at": "2026-10-20T09:00:00.000Z",
  "media": [
    {
      "url": "https://cdn.example.com/launch.jpg",
      "type": "image"
    }
  ],
  "accounts": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "platform": "instagram",
      "handle": "brand",
      "display_name": "Brand",
      "caption": null,
      "options": {},
      "status": "scheduled",
      "url": null,
      "error": null
    },
    {
      "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "platform": "x",
      "handle": "brand",
      "display_name": "Brand",
      "caption": "Launch day. Link in bio.",
      "options": {
        "who_can_reply": "following"
      },
      "status": "scheduled",
      "url": null,
      "error": null
    }
  ],
  "dashboard_url": "https://postwing.io/dashboard/post/7c1d2a8e-4b0f-4f8e-9a51-2f7f6b1c9d10",
  "created_at": "2026-10-14T12:00:00.000Z",
  "updated_at": "2026-10-14T12:00:00.000Z"
}
FieldMeaning
captionThe post text. Each account uses it unless that account sets its own caption.
accounts[].captionnull means “use the post caption”. "" means no caption on that account.
accounts[].optionsPlatform settings for that account. See Platform options.
accounts[].statusscheduled → publishing → published or failed. paused while the subscription is inactive.
accounts[].urlLink to the live post once published.
accounts[].errorWhy that account failed, when status is failed.
statusscheduled, publishing, published, partially_failed (some accounts failed), failed, or paused.

Media

Each media item is { url, type?, cover? }. Upload a file first, or use any public HTTPS URL.

  1. POST /media/uploads with filename, content_type, and size
  2. PUT the file bytes to upload_url (same Content-Type)
  3. Put the returned { url, type } in media when you schedule
FieldMeaning
urlPublic HTTPS URL of the image or video.
typeimage or video. Optional; inferred from the URL when you leave it out.
coverVideos only. { "url": "…" } for a cover image, or { "at_ms": 1500 } to use a frame (default 1000ms).
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
CoversJPG, PNG, WEBP10MB

Text-only posts work on Facebook, LinkedIn, X, and Threads. Platforms cap images per post; extra images are left off that platform:

PlatformMax images
Instagram10
Facebook20
LinkedIn20
X4
Threads4
TikTok30
Pinterest1
YouTubevideo only

Platform options

Set on accounts[].options. Only the keys for that account’s platform are accepted; anything else returns invalid_option.

X (Twitter)

x

OptionTypeDescription
quote_poststringPost URL or ID to quote. Creates a native quote post.
who_can_replyeveryone | following | mentioned | subscribers | verifiedWho can reply. Default everyone.

Instagram

instagram

OptionTypeDescription
share_to_feedbooleanReels only. Also show the Reel on the main feed. Default true.
trial_reeloff | manual | performanceReels only. Post as a trial Reel. Default off.

Facebook

facebook

OptionTypeDescription
formatpost | reelpost (feed) or reel. Videos default to reel, everything else to post.

YouTube

youtube

OptionTypeDescription
title*stringVideo title. Required.
made_for_kidsbooleanWhether the video is made for kids. Default false.

TikTok

tiktok

OptionTypeDescription
titlestringPhoto posts only. Ignored for videos.
visibilitypublic | privateDefault public.
allow_commentsbooleanDefault true.
allow_duetsbooleanDefault true.
allow_stitchesbooleanDefault true.
ai_generatedbooleanLabel the post as AI-generated. Default false.
save_as_draftbooleanSend to TikTok drafts instead of publishing. Default false.

Pinterest

pinterest

OptionTypeDescription
board_id*stringBoard to pin to. Required. Get IDs from GET /accounts/{id}/pinterest-boards.
titlestringPin title.
linkstringDestination URL when someone clicks the pin.

LinkedIn

linkedin

No options. Caption only.

Threads

threads

No options. Caption only.

* Required for that platform.

Errors & limits

Errors share one shape. Branch on code; show message to people; field points at the part of the request to fix.

Error (400)
{
  "error": {
    "code": "missing_option",
    "message": "title is required for youtube.",
    "field": "accounts[1].options.title"
  }
}
CodeStatusMeaning
unauthorized401Missing or invalid API key or OAuth token
missing_scope403The credential lacks the scope this endpoint needs
subscription_required403No active PostWing subscription (or the workspace owner’s plan lapsed)
plan_upgrade_required403The feature needs a higher plan (e.g. workspaces on Scale)
account_limit_reached403Your plan’s connected-account limit is reached
invalid_json400The body is missing or not a JSON object
unknown_field400A field v2 does not accept. v1 names come with a hint for the v2 name.
missing_field / invalid_field400A required field is missing or has the wrong type
invalid_scheduled_at400scheduled_at is not ISO 8601 or not in the future
invalid_media400Media URL, type, cover, or count does not fit the post or its platforms
invalid_option / missing_option400An option is unknown for that platform, has a bad value, or is required
account_not_found400 / 404The account does not exist or is not yours (400 in a request body, 404 in the URL)
duplicate_account400The same account appears twice in accounts
accounts_in_different_workspaces400One post can only go to accounts from one workspace
invalid_parameter400A query parameter has a bad value
nothing_to_update400PATCH had no fields to change
post_not_found / workspace_not_found404Not found, or not visible to you
post_not_editable / post_not_cancellable409The post already published, failed, or is about to publish
rate_limited429Too many requests; wait a moment and retry
publishing_unavailable502 / 503The publishing service is temporarily unavailable; retry
internal_error500Something went wrong on our side; retry
  • Requests: about 240/min per credential
  • Writes (schedule, edit, cancel, connect): about 40/min per credential
  • Media uploads: about 80/min per user

Posts

Schedule, read, edit, and cancel posts. Every call returns the same Post object.

POST/api/v2/postsposts:write

Schedule a post

Validates and schedules a post in one call.

The post goes on your PostWing calendar exactly like a dashboard post and publishes to every account at scheduled_at. To schedule several posts, call this once per post.

Request body

NameTypeRequiredDescription
captionstringYesPost text. "" for no caption.
scheduled_atISO 8601 datetimeYesWhen to publish. Must be in the future.
accountsobject[]YesWhere to post. Each item: { id, caption?, options? }. id comes from GET /accounts.
accounts[].captionstring | nullNoCaption for this account only. Omit to use the post caption; "" for none on this account.
accounts[].optionsobjectNoSettings for that account’s platform. See Platform options.
mediaobject[]NoImages or one video: { url, type?, cover? }. See Media.
workspace_idstring (uuid)NoDefaults to the workspace the accounts belong to.
Request
curl -s -X POST "https://postwing.io/api/v2/posts" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "caption": "Launch day 🚀",
  "scheduled_at": "2026-10-20T09:00:00Z",
  "accounts": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    },
    {
      "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "caption": "Launch day. Link in bio.",
      "options": {
        "who_can_reply": "following"
      }
    }
  ],
  "media": [
    {
      "url": "https://cdn.example.com/launch.jpg"
    }
  ]
}'
Response (201)
{
  "id": "7c1d2a8e-4b0f-4f8e-9a51-2f7f6b1c9d10",
  "workspace_id": "3f2a9c1e-8d4b-4e6a-b2c7-1a9e5d3f7b20",
  "status": "scheduled",
  "caption": "Launch day 🚀",
  "scheduled_at": "2026-10-20T09:00:00.000Z",
  "media": [
    {
      "url": "https://cdn.example.com/launch.jpg",
      "type": "image"
    }
  ],
  "accounts": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "platform": "instagram",
      "handle": "brand",
      "display_name": "Brand",
      "caption": null,
      "options": {},
      "status": "scheduled",
      "url": null,
      "error": null
    },
    {
      "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "platform": "x",
      "handle": "brand",
      "display_name": "Brand",
      "caption": "Launch day. Link in bio.",
      "options": {
        "who_can_reply": "following"
      },
      "status": "scheduled",
      "url": null,
      "error": null
    }
  ],
  "dashboard_url": "https://postwing.io/dashboard/post/7c1d2a8e-4b0f-4f8e-9a51-2f7f6b1c9d10",
  "created_at": "2026-10-14T12:00:00.000Z",
  "updated_at": "2026-10-14T12:00:00.000Z"
}
GET/api/v2/postsposts:read

List posts

Upcoming posts first, paginated.

Returns posts scheduled from now on unless you pass include_past=true or from. Keep calling with offset while has_more is true.

Query parameters

NameTypeRequiredDescription
workspace_idstring (uuid)NoWorkspace to use. Defaults to the active workspace.
statusstringNoscheduled | publishing | published | partially_failed | failed | paused
fromISO datetimeNoscheduled_at ≥ from
untilISO datetimeNoscheduled_at ≤ until
include_pastbooleanNoInclude posts before now
limitintegerNo1–100, default 20
offsetintegerNoDefault 0
Request
curl -s "https://postwing.io/api/v2/posts?limit=20&offset=0" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "data": [
    {
      "id": "7c1d2a8e-4b0f-4f8e-9a51-2f7f6b1c9d10",
      "workspace_id": "3f2a9c1e-8d4b-4e6a-b2c7-1a9e5d3f7b20",
      "status": "scheduled",
      "caption": "Launch day 🚀",
      "scheduled_at": "2026-10-20T09:00:00.000Z",
      "media": [
        {
          "url": "https://cdn.example.com/launch.jpg",
          "type": "image"
        }
      ],
      "accounts": [
        {
          "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "platform": "instagram",
          "handle": "brand",
          "display_name": "Brand",
          "caption": null,
          "options": {},
          "status": "scheduled",
          "url": null,
          "error": null
        },
        {
          "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
          "platform": "x",
          "handle": "brand",
          "display_name": "Brand",
          "caption": "Launch day. Link in bio.",
          "options": {
            "who_can_reply": "following"
          },
          "status": "scheduled",
          "url": null,
          "error": null
        }
      ],
      "dashboard_url": "https://postwing.io/dashboard/post/7c1d2a8e-4b0f-4f8e-9a51-2f7f6b1c9d10",
      "created_at": "2026-10-14T12:00:00.000Z",
      "updated_at": "2026-10-14T12:00:00.000Z"
    }
  ],
  "total": 42,
  "limit": 20,
  "offset": 0,
  "has_more": true
}
GET/api/v2/posts/{id}posts:read

Get a post

One post, including each account’s status, link, and error.

After publishing, accounts[].status is published (with url) or failed (with error). The post status is published, partially_failed, or failed.

Path parameters

NameTypeRequiredDescription
idstring (uuid)YesPost id
Request
curl -s "https://postwing.io/api/v2/posts/7c1d2a8e-4b0f-4f8e-9a51-2f7f6b1c9d10" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "id": "7c1d2a8e-4b0f-4f8e-9a51-2f7f6b1c9d10",
  "workspace_id": "3f2a9c1e-8d4b-4e6a-b2c7-1a9e5d3f7b20",
  "status": "partially_failed",
  "caption": "Launch day 🚀",
  "scheduled_at": "2026-10-20T09:00:00.000Z",
  "media": [
    {
      "url": "https://cdn.example.com/launch.jpg",
      "type": "image"
    }
  ],
  "accounts": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "platform": "instagram",
      "handle": "brand",
      "display_name": "Brand",
      "caption": null,
      "options": {},
      "status": "published",
      "url": "https://www.instagram.com/p/Cx1y2z3/",
      "error": null
    },
    {
      "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "platform": "x",
      "handle": "brand",
      "display_name": "Brand",
      "caption": "Launch day. Link in bio.",
      "options": {
        "who_can_reply": "following"
      },
      "status": "failed",
      "url": null,
      "error": "Reconnect your X account and try again."
    }
  ],
  "dashboard_url": "https://postwing.io/dashboard/post/7c1d2a8e-4b0f-4f8e-9a51-2f7f6b1c9d10",
  "created_at": "2026-10-14T12:00:00.000Z",
  "updated_at": "2026-10-14T12:00:00.000Z"
}
PATCH/api/v2/posts/{id}posts:write

Edit a post

Change a scheduled post. Send only what changes.

Fields you leave out keep their current value. accounts replaces the whole list, so include caption and options for every account you keep. media: [] removes all media. Only scheduled posts can be edited.

Path parameters

NameTypeRequiredDescription
idstring (uuid)YesPost id

Request body

NameTypeRequiredDescription
captionstringNoNew caption
scheduled_atISO datetimeNoNew time, in the future
accountsobject[]NoReplaces the full account list
mediaobject[]NoReplaces media; [] removes it
Request
curl -s -X PATCH "https://postwing.io/api/v2/posts/7c1d2a8e-4b0f-4f8e-9a51-2f7f6b1c9d10" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "scheduled_at": "2026-10-21T09:00:00Z"
}'
Response (200)
{
  "id": "7c1d2a8e-4b0f-4f8e-9a51-2f7f6b1c9d10",
  "workspace_id": "3f2a9c1e-8d4b-4e6a-b2c7-1a9e5d3f7b20",
  "status": "scheduled",
  "caption": "Launch day 🚀",
  "scheduled_at": "2026-10-21T09:00:00.000Z",
  "media": [
    {
      "url": "https://cdn.example.com/launch.jpg",
      "type": "image"
    }
  ],
  "accounts": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "platform": "instagram",
      "handle": "brand",
      "display_name": "Brand",
      "caption": null,
      "options": {},
      "status": "scheduled",
      "url": null,
      "error": null
    },
    {
      "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "platform": "x",
      "handle": "brand",
      "display_name": "Brand",
      "caption": "Launch day. Link in bio.",
      "options": {
        "who_can_reply": "following"
      },
      "status": "scheduled",
      "url": null,
      "error": null
    }
  ],
  "dashboard_url": "https://postwing.io/dashboard/post/7c1d2a8e-4b0f-4f8e-9a51-2f7f6b1c9d10",
  "created_at": "2026-10-14T12:00:00.000Z",
  "updated_at": "2026-10-14T12:00:00.000Z"
}
DELETE/api/v2/posts/{id}posts:write

Cancel a post

Removes a scheduled or paused post before it publishes.

Posts that already published, or are publishing now, cannot be cancelled.

Path parameters

NameTypeRequiredDescription
idstring (uuid)YesPost id
Request
curl -s -X DELETE "https://postwing.io/api/v2/posts/7c1d2a8e-4b0f-4f8e-9a51-2f7f6b1c9d10" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "id": "7c1d2a8e-4b0f-4f8e-9a51-2f7f6b1c9d10",
  "cancelled": true
}
GET/api/v2/posts/summaryposts:read

Posts summary

Counts by platform, account, status, and day.

Use this instead of paging every post when you only need totals. Takes the same workspace_id, status, from, until, and include_past filters as the list, plus platform.

Query parameters

NameTypeRequiredDescription
workspace_idstring (uuid)NoWorkspace to use. Defaults to the active workspace.
platformstringNoOnly count this platform
Request
curl -s "https://postwing.io/api/v2/posts/summary" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "timezone": "America/New_York",
  "total_posts": 15,
  "by_platform": {
    "instagram": 10,
    "x": 8
  },
  "by_status": {
    "scheduled": 15
  }
}

Accounts

The social accounts you post to.

GET/api/v2/accountsaccounts:read

List accounts

Connected accounts in a workspace.

Use each id in POST /posts accounts[]. Filter by platform to find, say, every Instagram account. status reauth_required means the user must reconnect it in PostWing.

Query parameters

NameTypeRequiredDescription
workspace_idstring (uuid)NoWorkspace to use. Defaults to the active workspace.
platformstringNoinstagram | tiktok | youtube | linkedin | x | facebook | pinterest | threads
Request
curl -s "https://postwing.io/api/v2/accounts?platform=instagram" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "data": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "platform": "instagram",
      "handle": "brand",
      "display_name": "Brand",
      "avatar_url": "https://cdn.example.com/avatar.jpg",
      "status": "connected",
      "workspace_id": "3f2a9c1e-8d4b-4e6a-b2c7-1a9e5d3f7b20"
    }
  ]
}
POST/api/v2/accounts/connectaccounts:write

Connect an account

Start the platform login and get a URL to open.

Open url in a browser and finish the login, then call POST /accounts/sync. New accounts go to the active workspace (POST /workspaces/active to change it).

Request body

NameTypeRequiredDescription
platformstringYesinstagram | tiktok | youtube | linkedin | x | facebook | pinterest | threads
Request
curl -s -X POST "https://postwing.io/api/v2/accounts/connect" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "platform": "instagram"
}'
Response (200)
{
  "url": "https://…",
  "platform": "instagram"
}
POST/api/v2/accounts/syncaccounts:write

Sync accounts

Import accounts the user just authorized.

Call after the user finishes the login from /accounts/connect.

Request
curl -s -X POST "https://postwing.io/api/v2/accounts/sync" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "data": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "platform": "instagram",
      "handle": "brand",
      "display_name": "Brand",
      "avatar_url": "https://cdn.example.com/avatar.jpg",
      "status": "connected",
      "workspace_id": "3f2a9c1e-8d4b-4e6a-b2c7-1a9e5d3f7b20"
    }
  ]
}
DELETE/api/v2/accounts/{id}accounts:write

Disconnect an account

Remove an account from PostWing.

Only do this when the user asks. Scheduled posts stop publishing to this account.

Path parameters

NameTypeRequiredDescription
idstring (uuid)YesAccount id
Request
curl -s -X DELETE "https://postwing.io/api/v2/accounts/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "disconnected": true
}
GET/api/v2/accounts/{id}/pinterest-boardsaccounts:read

List Pinterest boards

Boards you can pin to.

Pass a board id as options.board_id when posting to this Pinterest account.

Path parameters

NameTypeRequiredDescription
idstring (uuid)YesPinterest account id
Request
curl -s "https://postwing.io/api/v2/accounts/PINTEREST_ACCOUNT_ID/pinterest-boards" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "data": [
    {
      "id": "1234567890",
      "name": "Marketing"
    }
  ]
}

Media

Get files to a public URL before you schedule.

POST/api/v2/media/uploadsposts:write

Upload a file

Get a short-lived upload URL for an image or video.

PUT the raw file bytes to upload_url with the same Content-Type. Then use { url, type } as a media item in POST /posts. The file never passes through PostWing’s servers.

Request body

NameTypeRequiredDescription
filenamestringYese.g. reel.mp4
content_typestringYese.g. video/mp4
sizeintegerYesBytes
Request
curl -s -X POST "https://postwing.io/api/v2/media/uploads" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "filename": "reel.mp4",
  "content_type": "video/mp4",
  "size": 12582912
}'

# then
curl -s -X PUT "<upload_url>" -H "Content-Type: video/mp4" --data-binary @reel.mp4
Response (201)
{
  "upload_url": "https://storage…",
  "url": "https://cdn…/reel.mp4",
  "type": "video"
}
POST/api/v2/media/importsposts:write

Import from URLs

Copy up to 10 public HTTPS files into PostWing storage.

Optional. Durable public URLs can go straight into POST /posts. Use this when a link might expire. Videos imported this way are limited to 100MB.

Request body

NameTypeRequiredDescription
urlsstring[]Yes1–10 HTTPS URLs
Request
curl -s -X POST "https://postwing.io/api/v2/media/imports" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "urls": [
    "https://example.com/photo.jpg"
  ]
}'
Response (201)
{
  "data": [
    {
      "url": "https://cdn…/photo.jpg",
      "type": "image"
    }
  ]
}

Workspaces

Separate brands or clients. Pass workspace_id on any call, or set a default.

GET/api/v2/workspacesworkspaces:read

List workspaces

Workspaces you own or were invited to.

is_active marks the default used when a call has no workspace_id.

Request
curl -s "https://postwing.io/api/v2/workspaces" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "data": [
    {
      "id": "3f2a9c1e-8d4b-4e6a-b2c7-1a9e5d3f7b20",
      "name": "Personal",
      "role": "owner",
      "is_active": true,
      "created_at": "2026-01-01T00:00:00.000Z"
    }
  ],
  "active_workspace_id": "3f2a9c1e-8d4b-4e6a-b2c7-1a9e5d3f7b20"
}
POST/api/v2/workspacesworkspaces:write

Create a workspace

Scale plan.

Creates an empty workspace you own.

Request body

NameTypeRequiredDescription
namestringYesWorkspace name
Request
curl -s -X POST "https://postwing.io/api/v2/workspaces" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Client: Acme"
}'
Response (201)
{
  "id": "9a8b…",
  "name": "Client: Acme",
  "role": "owner",
  "is_active": false,
  "created_at": "2026-10-14T12:00:00.000Z"
}
POST/api/v2/workspaces/activeworkspaces:write

Set the active workspace

Default for calls without workspace_id, and where connected accounts go.

Also changes the workspace the user sees in the dashboard.

Request body

NameTypeRequiredDescription
workspace_idstring (uuid)YesWorkspace id
Request
curl -s -X POST "https://postwing.io/api/v2/workspaces/active" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "workspace_id": "3f2a9c1e-8d4b-4e6a-b2c7-1a9e5d3f7b20"
}'
Response (200)
{
  "id": "3f2a9c1e-8d4b-4e6a-b2c7-1a9e5d3f7b20",
  "name": "Personal",
  "role": "owner",
  "is_active": true,
  "created_at": "2026-01-01T00:00:00.000Z"
}

You

Who the credential belongs to.

GET/api/v2/mepublic

Get the current user

Timezone and default workspace.

Use timezone to turn “tomorrow at 9am” into scheduled_at.

Request
curl -s "https://postwing.io/api/v2/me" \
  -H "Authorization: Bearer pw_live_…"
Response (200)
{
  "id": "u-…",
  "email": "you@example.com",
  "timezone": "America/New_York",
  "active_workspace_id": "3f2a9c1e-8d4b-4e6a-b2c7-1a9e5d3f7b20"
}

Examples

Copy-paste patterns.

Text post to LinkedIn and X

1. Find the accounts

curl -s "https://postwing.io/api/v2/accounts" \
  -H "Authorization: Bearer pw_live_…"

2. Schedule

curl -s -X POST "https://postwing.io/api/v2/posts" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "caption": "We are hiring. Details on our site.",
  "scheduled_at": "2026-10-20T15:00:00Z",
  "accounts": [
    {
      "id": "LINKEDIN_ACCOUNT_ID"
    },
    {
      "id": "X_ACCOUNT_ID",
      "caption": "We’re hiring 👀"
    }
  ]
}'

Upload a video and schedule a Reel with a cover

1. Upload

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

2. PUT the bytes to upload_url

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

3. Schedule

curl -s -X POST "https://postwing.io/api/v2/posts" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "caption": "Behind the scenes",
  "scheduled_at": "2026-10-20T18:00:00Z",
  "accounts": [
    {
      "id": "INSTAGRAM_ACCOUNT_ID",
      "options": {
        "share_to_feed": true
      }
    }
  ],
  "media": [
    {
      "url": "https://cdn…/reel.mp4",
      "type": "video",
      "cover": {
        "url": "https://cdn…/cover.jpg"
      }
    }
  ]
}'

One video to YouTube and TikTok

Schedule

curl -s -X POST "https://postwing.io/api/v2/posts" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "caption": "How we pack 1,000 orders a day",
  "scheduled_at": "2026-10-21T16:00:00Z",
  "accounts": [
    {
      "id": "YOUTUBE_ACCOUNT_ID",
      "options": {
        "title": "Packing 1,000 orders a day"
      }
    },
    {
      "id": "TIKTOK_ACCOUNT_ID",
      "options": {
        "visibility": "public",
        "allow_duets": false
      }
    }
  ],
  "media": [
    {
      "url": "https://cdn…/packing.mp4",
      "cover": {
        "at_ms": 3000
      }
    }
  ]
}'

Pinterest pin

1. Pick a board

curl -s "https://postwing.io/api/v2/accounts/PINTEREST_ACCOUNT_ID/pinterest-boards" \
  -H "Authorization: Bearer pw_live_…"

2. Schedule the pin

curl -s -X POST "https://postwing.io/api/v2/posts" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "caption": "New lookbook",
  "scheduled_at": "2026-10-22T12:00:00Z",
  "accounts": [
    {
      "id": "PINTEREST_ACCOUNT_ID",
      "options": {
        "board_id": "1234567890",
        "title": "Fall lookbook",
        "link": "https://example.com/shop"
      }
    }
  ],
  "media": [
    {
      "url": "https://cdn.example.com/pin.jpg"
    }
  ]
}'

Agencies: post into a client workspace

1. Find the client’s accounts

curl -s "https://postwing.io/api/v2/accounts?workspace_id=CLIENT_WORKSPACE_ID" \
  -H "Authorization: Bearer pw_live_…"

2. Schedule (the post lands in the accounts’ workspace)

curl -s -X POST "https://postwing.io/api/v2/posts" \
  -H "Authorization: Bearer pw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "caption": "Weekend sale starts now",
  "scheduled_at": "2026-10-24T08:00:00Z",
  "accounts": [
    {
      "id": "CLIENT_INSTAGRAM_ID"
    }
  ],
  "media": [
    {
      "url": "https://cdn.example.com/sale.jpg"
    }
  ]
}'

Migrating from v1

v1 keeps working, so move when it suits you. Point your base URL at /api/v2 and rename these fields.

v1v2
Base URL /api/v1Base URL /api/v2
POST /posts/preview, then POST /posts/confirmPOST /posts (one call)
POST /posts/edit/preview, then /posts/edit/confirmPATCH /posts/{id} (one post per call)
DELETE /posts/{id} with { "confirm": true }DELETE /posts/{id} (no body)
targets[].account_id, account_ids, platformsaccounts: [{ "id": … }] (ids from GET /accounts?platform=…)
targets[].caption, targets[].platform_options.captionaccounts[].caption
targets[].platform_optionsaccounts[].options (renamed keys below)
media_items, media_urlsmedia
media_items[].thumbnail_url / thumbnail_timestamp_msmedia[].cover.url / media[].cover.at_ms
post_typeRemoved (inferred from media)
caption " " for emptycaption ""
Facebook placement: timeline | reelsformat: post | reel
Instagram trial_reel_type: null | manual | performancetrial_reel: off | manual | performance
Instagram placementRemoved (set automatically)
TikTok privacy_statusvisibility
TikTok allow_comment / allow_duet / allow_stitchallow_comments / allow_duets / allow_stitches
TikTok is_ai_generated / is_draftai_generated / save_as_draft
Pinterest board_ids: ["…"]board_id: "…"
X quote_tweet_urlquote_post
X reply_settings: null | mentionedUsers | …who_can_reply: everyone | mentioned | …
tiktok_business platformtiktok
Post fields caption_common, post_targets, view_urlcaption, accounts, dashboard_url
account_id in account responsesid
{ "error": "message" }{ "error": { "code", "message", "field" } }
GET /accounts → { timezone, accounts }GET /accounts → { data }; timezone from GET /me
GET /schedule/summaryGET /posts/summary
POST /accounts (connect), DELETE /accounts with bodyPOST /accounts/connect, DELETE /accounts/{id}
GET /accounts/pinterest-boards?account_id=…GET /accounts/{id}/pinterest-boards
POST /media/upload-url → media_urlPOST /media/uploads → url
POST /media → media_itemsPOST /media/imports → data

Sending a v1 field name to v2 returns unknown_field with the v2 name in the message. The full v1 reference stays at /docs/v1.

MCP tools

Claude, ChatGPT, and Cursor connect over MCP at https://postwing.io/api/mcp. In chat, the assistant shows you a preview and schedules only after you approve.

ToolScopeSummary
get_me—Timezone and active workspace
list_accountsaccounts:readConnected accounts (filter by platform or workspace)
connect_accountaccounts:writeStart connecting an account; returns a browser URL
sync_accountsaccounts:writeImport accounts after the user finishes connecting
disconnect_accountaccounts:writeDisconnect an account (only when the user asks)
list_pinterest_boardsaccounts:readBoards for options.board_id
list_workspacesworkspaces:readWorkspaces and which one is active
create_workspaceworkspaces:writeCreate a workspace (Scale plan)
set_active_workspaceworkspaces:writeSet the default workspace
request_media_uploadposts:writeOpen the in-chat upload widget
import_mediaposts:writeCopy media from public HTTPS URLs
schedule_postposts:writeSchedule a post, after the user approves it
get_postposts:readOne post with each account’s status, link, and error
list_postsposts:readPosts, upcoming first (paginated)
get_posts_summaryposts:readCounts by platform, account, status, and day
update_postposts:writeChange one scheduled post
cancel_postposts:writeCancel a post before it publishes

Access requirements

  • An active PostWing subscription (7-day free trial available)
  • Connected social accounts in your workspace (or connect them through the API)
  • An API key or OAuth token with the scopes you need
  • Platforms: Instagram, TikTok, YouTube, LinkedIn, X, Facebook, Pinterest, Threads

Prefer chat?

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