Create an API key
Open Dashboard → AI & API and create a key. Copy it once. It starts with pw_live_. Send it on every request:
Header
Authorization: Bearer pw_live_…
Every URL below starts at https://postwing.io/api/v1.
the whole flow
You call PostWing. PostWing publishes to Threads at the time you set. The post also shows up on your calendar.
Open Dashboard → AI & API and create a key. Copy it once. It starts with pw_live_. Send it on every request:
Header
Authorization: Bearer pw_live_…
Every URL below starts at https://postwing.io/api/v1.
Connect the Threads account you want to post from. You only do this once.
The simple way is in the dashboard. Open Accounts, choose Threads, and finish the Threads login.
From the API, start that same login. The response includes a url. Open it and approve Threads.
Start connect
curl -s -X POST "https://postwing.io/api/v1/accounts" \
-H "Authorization: Bearer pw_live_…" \
-H "Content-Type: application/json" \
-d '{ "platform": "threads" }'After you finish in the browser, sync. This imports the account into your workspace and returns the account id.
Sync
curl -s -X POST "https://postwing.io/api/v1/accounts/sync" \ -H "Authorization: Bearer pw_live_…"
In the response, find the object where platform is threads and copy account_id. That is the id you send when you create the post.
Sync response
{
"synced_count": 1,
"accounts": [
{
"account_id": "b8c9d0e1-f2a3-4567-8901-678901234567",
"platform": "threads",
"handle": "brand",
"display_name": "Brand",
"status": "connected"
}
]
}Connected Threads in the dashboard instead? List accounts and copy the same field:
List accounts
curl -s -X GET "https://postwing.io/api/v1/accounts" \ -H "Authorization: Bearer pw_live_…"
A caption is enough on Threads. Send the text, a future time, and the Threads account_id you copied after sync. There are no extra platform options. This call creates the post. Threads does not publish until you confirm in the next step. scheduled_at is UTC.
Create a text post
curl -s -X POST "https://postwing.io/api/v1/posts/preview" \
-H "Authorization: Bearer pw_live_…" \
-H "Content-Type: application/json" \
-d '{
"caption": "We just shipped a faster way to publish updates.",
"scheduled_at": "2026-10-12T15:00:00Z",
"targets": [
{ "account_id": "b8c9d0e1-f2a3-4567-8901-678901234567" }
]
}'You get back three things that matter:
Response
{
"preview": {
"caption": "We just shipped a faster way to publish updates.",
"scheduled_at": "2026-10-12T15:00:00.000Z",
"post_type": "text",
"targets": [
{
"account_id": "b8c9d0e1-f2a3-4567-8901-678901234567",
"platform": "threads",
"handle": "brand"
}
]
},
"confirmation_token": "eyJ…signature",
"expires_in_seconds": 300
}A Threads caption is capped at about 500 characters.
The same text can go to X and LinkedIn. Connect each channel the way you connected Threads, then copy its account_id from the sync response.
Connect X
curl -s -X POST "https://postwing.io/api/v1/accounts" \
-H "Authorization: Bearer pw_live_…" \
-H "Content-Type: application/json" \
-d '{ "platform": "x" }'Connect LinkedIn
curl -s -X POST "https://postwing.io/api/v1/accounts" \
-H "Authorization: Bearer pw_live_…" \
-H "Content-Type: application/json" \
-d '{ "platform": "linkedin" }'Open each url, finish the login, then sync once. Copy account_id where platform is x, and where platform is linkedin.
Add those ids to targets on the create call. One request, one confirm. Every account publishes at the same scheduled_at. Leave caption off a target to use the shared caption. Set caption on Threads when the shared text is longer than Threads allows.
Create one text post for three channels
curl -s -X POST "https://postwing.io/api/v1/posts/preview" \
-H "Authorization: Bearer pw_live_…" \
-H "Content-Type: application/json" \
-d '{
"caption": "We just shipped a faster way to publish updates from one request.",
"scheduled_at": "2026-10-12T15:00:00Z",
"targets": [
{
"account_id": "b8c9d0e1-f2a3-4567-8901-678901234567",
"caption": "Faster publishing, now live."
},
{ "account_id": "e5f6a7b8-c9d0-1234-ef01-345678901234" },
{ "account_id": "d4e5f6a7-b8c9-0123-def0-234567890123" }
]
}'Facebook accepts this text with no media. Instagram, TikTok, Pinterest, and YouTube need an image or a video. Pinterest also needs board_ids. YouTube also needs a title.
When the Threads preview looks right, confirm with the token from that response. Use the token as-is. Do not invent one.
Confirm
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" }'The response includes view_url. Open it to see the post on your calendar. Threads publishes at the time you set. X and LinkedIn publish then too, when you added them. You can edit or cancel later from the dashboard or with the same API.
If confirm says the token expired, create the post again and confirm the new token within 5 minutes.
media
Media is optional. Use the same Threads account_id. Images go out with the caption, up to 4. A video is one file.
Create an image post
curl -s -X POST "https://postwing.io/api/v1/posts/preview" \
-H "Authorization: Bearer pw_live_…" \
-H "Content-Type: application/json" \
-d '{
"caption": "A look at the new office.",
"scheduled_at": "2026-10-12T18:00:00Z",
"targets": [
{ "account_id": "b8c9d0e1-f2a3-4567-8901-678901234567" }
],
"media_items": [
{ "url": "https://cdn.example.com/office.jpg", "type": "image" }
]
}'Add more image objects in media_items, up to 4. For a video, send one item with type video instead. Threads has no extra platform_options.
The image or video URL has to be public HTTPS. Videos imported from a URL need to be 100MB or smaller. A file on your computer uses the direct upload URL, covered in the API docs.
questions
Yes. Create the post with caption, scheduled_at, and the Threads account_id. Leave out media_items. Then confirm with the token.