Publish and schedule posts

Create drafts, threads and posts with media, then schedule them for a time or publish them now.

One endpoint creates every post. What you send decides what happens to it:

You sendPostel
Neither scheduledAt nor publishNowSaves a draft. Works on every plan.
scheduledAtSaves the post and schedules it. Needs a paid plan.
publishNow: trueSaves the post and publishes it within a minute. Needs a paid plan.

You can also save a draft first and schedule or publish it later by its id.

Networks

NetworkplatformCharacters per postThreadsMedia per post
Xx280 per part, or 25,000 if the connected X account has PremiumUp to 25 partsUp to 4 images, or 1 video, or 1 GIF
LinkedInlinkedin3,000NoUp to 4 images, or 1 video
Blueskybluesky300Up to 25 partsUp to 4 images
Mastodonmastodon500Up to 25 partsUp to 4 images, or 1 video, or 1 GIF

GET /v1/channels shows which of these are connected in your workspace. It can also list networks the API doesn't post to yet, such as YouTube and TikTok, which need video settings the API can't set. Post to those from the Postel app.

Postel checks the length of every part before it saves anything. A part over the limit gets 400 text_too_long, and the message says which part and by how much. Bluesky counts characters the way people see them, so an emoji counts as one.

Create a post

FieldTypeDescription
platformstringRequired. x, linkedin, bluesky or mastodon.
textstringThe post. Send text or thread, not both.
threadstring[]A thread, one item per part, 1 to 25 items.
mediaUrlsstring[]Up to 4 public image or video URLs. Postel downloads them and attaches them to the first part.
firstCommentstringLinkedIn only. A comment Postel posts under the post right after it goes out. Up to 1,250 characters.
scheduledAtstringWhen to publish. ISO 8601 with a timezone, in the future.
publishNowbooleanPublish within a minute. Don't send it with scheduledAt.

It answers 201 with the post. Send an Idempotency-Key so a retried request can't create the post twice.

curl https://www.postel.app/api/v1/posts \
  -H "Authorization: Bearer $POSTEL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: launch-post-2026-11-04" \
  -d '{
    "platform": "linkedin",
    "text": "Our public API is live. Here is what we built and why.",
    "firstComment": "Docs: https://www.postel.app/docs",
    "scheduledAt": "2026-12-02T09:30:00+01:00"
  }'

Threads

On X, Bluesky and Mastodon you can write a thread in two ways.

Send thread with one item per part:

{
  "platform": "x",
  "thread": [
    "Most launch posts fail for one reason.",
    "They explain the product before the problem.",
    "Start with the problem. The product is the second post."
  ]
}

Or send text with a line that holds only --- between parts, the same way the Postel editor works:

{
  "platform": "x",
  "text": "Most launch posts fail for one reason.\n---\nThey explain the product before the problem."
}

With thread, a --- line inside a part stays as text and doesn't split it. Each part has its own character limit. On X, that is 280 per part unless the connected X account has Premium.

LinkedIn has no threads. A thread with more than one part for LinkedIn gets 400 invalid_request, and a --- line in LinkedIn text stays as text.

The response shows the thread both ways: thread holds the parts, and text holds the whole post with the parts joined by a blank line.

Media

mediaUrls takes up to 4 public http or https URLs. Postel downloads each file before it saves the post and attaches them to the first part of a thread.

  • Postel reads the file type from the file itself, not from the URL or the Content-Type header.
  • The URL must answer within 30 seconds, without a login.
  • One video or one GIF stands alone. You can't mix it with other files in one post.
NetworkFile typesLargest file
XJPEG, PNG, WebP, GIF, MP4, MOV, WebMImages 5 MB, GIFs 15 MB, videos 100 MB
LinkedInJPEG, PNG, GIF, MP4Images and GIFs 10 MB, videos 100 MB
BlueskyJPEG, PNG, WebPImages 1 MB
MastodonJPEG, PNG, WebP, GIF, MP4, MOV, WebMImages and GIFs 16 MB, videos 99 MB

On LinkedIn a GIF posts as an image. A URL Postel can't use gets 400 invalid_request, and error starts with that URL and says why.

LinkedIn first comment

firstComment posts a comment under your LinkedIn post right after it goes out. Use it for a link you want to keep out of the post itself. It takes up to 1,250 characters. On any other network it gets 400 invalid_request.

Schedule a draft

FieldTypeDescription
scheduledAtstringRequired. ISO 8601 with a timezone, in the future.
firstCommentstringLinkedIn only. Replaces the saved first comment. An empty string removes it.

It answers 200 with the post, now scheduled. Only drafts can be scheduled. Anything else gets 409 not_a_draft.

Times and timezones

scheduledAt needs a timezone, either Z or an offset: 2026-12-01T08:30:00Z and 2026-12-01T09:30:00+01:00 are the same moment. A time without one gets 400 invalid_request. Postel returns every time in UTC.

GET /v1/me returns the workspace's timezone if you want to schedule in the user's local time.

If the workspace has Vary posting times a little turned on in its posting schedule, a post goes out up to 4 minutes before or after scheduledAt.

One post per network per moment

Two posts on the same network can't be scheduled for the exact same time. The second one gets 409 time_taken. Posts on different networks can share a time.

Publish now

Send publishNow: true when you create a post, or publish an existing draft:

Publishing doesn't happen inside your request. Postel queues the post and answers right away with status scheduled and a message. POST /v1/posts/{id}/publish answers 202, and POST /v1/posts with publishNow answers 201. The post goes out within a minute.

This way a slow network never makes your request time out, and a retried request can't post twice: the second call gets 409 not_a_draft because the post is already on its way.

To learn how it went, poll post.published and post.failed events, or read the post with GET /v1/posts/{id} until its status is published or it is back to draft with a failure.

Publishing now is refused with 409 approval_required when the workspace needs posts approved. Schedule the post instead. See Approvals and plans.

When publishing fails

If the network refuses a post, Postel moves it back to draft and fills in failure:

{
  "status": "draft",
  "failure": {
    "message": "Postel's connection to X has expired. Reconnect it in Settings → Channels.",
    "type": "auth_expired",
    "at": "2026-12-01T08:30:04.512Z",
    "partlyPublished": false
  }
}

failure.type is one of auth_expired, invalid_content, rate_limited or platform_error, and new types can appear. Fix the cause, then schedule or publish the draft again.

A partly published thread

Sometimes the first parts of a thread go out and a later part is refused. Then the post stays published, url links to the first part, and failure.partlyPublished is true. It also fires a post.failed event.

Never resend a partly published thread. The first part is already live, and sending it again posts it twice. Finish the thread by hand on the network.

Read posts

Returns one post, or 404 not_found if there is no post with that id in the workspace.

Lists posts newest first. Filter with status and platform, and page with cursor. See Pagination.

The post object

FieldTypeDescription
idstringPostel's id for the post. Stable.
platformstringThe network, like x or linkedin.
statusstringdraft, scheduled, published or pending_approval.
textstringThe whole post as plain text. Thread parts are joined by a blank line.
threadstring[]Each part in order. One item for a single post.
firstCommentstring or nullLinkedIn first comment.
mediaCountintegerHow many images or videos are attached.
scheduledAtstring or nullWhen it is scheduled to go out, UTC ISO 8601.
publishedAtstring or nullWhen it went out, UTC ISO 8601.
urlstring or nullLink to the published post on its network.
failureobject or nullWhy the last publish failed. See When publishing fails.
createdAtstringUTC ISO 8601.
updatedAtstringUTC ISO 8601.

Responses from creating, scheduling and publishing can also include message, a sentence about what happens next, for example that the post waits for approval.

On this page