Zernio
APIFor you
Zernio
APIFor you
Platforms

X

Overview

Publish

Posts & editingReplies & quotesMedia & videoFields, geo & polls

Engage

Analytics & engagementInbox

Reference

Limits & errors
Dashboard
OverviewPlatformsAPI ReferenceResources
X

Posts & editing

Create a post on X, attach images, a GIF or a video, publish a thread with threadItems, publish an Article and edit a published post.


By the end of this page you have published a post, a post with media, a thread and a long-form Article to X (platform value twitter) with POST /v1/posts. You have also edited a published post with POST /v1/posts/{postId}/edit.

Before you start

You need an API key and a connected X account (accountId, from the X page).

Connecting takes a card on file. X bills every API call and Zernio passes that cost through. Until the team has a payment method, GET /v1/connect/twitter answers 402 with reason: "twitter_passthrough".

Step 1: create a post

Call POST /v1/posts with content, a platforms entry with platform: "twitter" and publishNow: true (Create post). Keep content under 280 characters on a free account.

To schedule instead, replace publishNow with scheduledFor and timezone. The post lifecycle guide covers the statuses.

import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data: published } = await zernio.posts.createPost({
  body: {
    content: 'Shipping day. The API is live.',
    platforms: [
      { platform: 'twitter', accountId: '66b2e19d8c3f5a7e9d0b1c2d' }
    ],
    publishNow: true
  }
});

console.log(published.post.platforms[0].platformPostUrl);

Response (201):

{
  "post": {
    "_id": "65f1c0a9e2b5af0012ab34cd",
    "status": "published",
    "platforms": [
      {
        "platform": "twitter",
        "status": "published",
        "platformPostId": "1852634789012345678",
        "platformPostUrl": "https://twitter.com/acmecorp/status/1852634789012345678"
      }
    ]
  }
}

Every sample below changes only mediaItems or the platforms entry of this request.

Step 2: attach media

Add mediaItems to the request. A post takes one of:

  • Up to 4 images (JPEG, PNG, WebP, up to 5 MB each).
  • 1 video (MP4 or MOV, up to 512 MB, the only limit Zernio enforces). X sets the duration per account.
  • 1 GIF (up to 15 MB and 1280 x 1080 px). It takes all 4 image slots.

The full limits are on Media & Video.

"mediaItems": [
  { "type": "image", "url": "https://cdn.example.com/photo.jpg" }
]

A video uses "type": "video" and a GIF "type": "gif":

"mediaItems": [
  { "type": "gif", "url": "https://cdn.example.com/animation.gif" }
]

The response is the one from Step 1.

Step 3: publish a thread

Put the whole sequence in platformSpecificData.threadItems. The first item is the root post, and each later item replies to the one before it.

Every item can carry its own mediaItems.

Top-level content is not published when threadItems is set. It is stored for display and search only, so the first post goes in threadItems[0].

const { data: thread } = await zernio.posts.createPost({
  body: {
    platforms: [{
      platform: 'twitter',
      accountId: '66b2e19d8c3f5a7e9d0b1c2d',
      platformSpecificData: {
        threadItems: [
          {
            content: '1/ A thread about API design',
            mediaItems: [{ type: 'image', url: 'https://cdn.example.com/image1.jpg' }]
          },
          { content: '2/ Use the HTTP method that matches the action.' },
          { content: '3/ Version the API from day one.' },
          { content: '4/ Document everything. /end' }
        ]
      }
    }],
    publishNow: true
  }
});

console.log(thread.post.platforms[0].platformPostUrl);

Response (201), where platformPostUrl points at the root post:

{
  "post": {
    "_id": "65f1c0a9e2b5af0012ab34cd",
    "status": "published",
    "platforms": [
      {
        "platform": "twitter",
        "status": "published",
        "platformPostUrl": "https://twitter.com/acmecorp/status/1852634789012345678"
      }
    ]
  }
}

To publish a thread as a reply to an existing post, see reply with a thread. A thread cannot carry a poll.

Step 4: edit a published post

Call POST /v1/posts/{postId}/edit with platform: "twitter" and the new content (Edit post).

X allows an edit only:

  • on accounts with an active X Premium subscription
  • within 1 hour of publishing
  • up to 5 times per post
  • on single posts (not threads)
  • for text only

X mints a new post id on every edit and returns it as id.

const { data: edited } = await zernio.posts.editPost({
  path: { postId: '65f1c0a9e2b5af0012ab34cd' },
  body: { platform: 'twitter', content: 'Shipping day. The API is live at zernio.com.' }
});

console.log(edited.id, edited.url);

Response (200):

{
  "success": true,
  "id": "1852634789012345690",
  "url": "https://twitter.com/i/web/status/1852634789012345690",
  "message": "twitter post edited successfully"
}

Several X accounts. Pass accountId in the body to pick which copy to edit. Without it, the first twitter entry on the post is edited.

A 400 means the edit window has passed, the account is not Premium, the post is a thread, or content is missing.

Publish an Article

platformSpecificData.article publishes a long-form X Article instead of a post, on an eligible X Premium+ account.

content_state is X's own snake_case content format, not free text. blocks carries the body, and entities the links and images the blocks point at.

Both keys are required, and entities may be empty. The smallest Article that publishes:

{
  "platformSpecificData": {
    "article": {
      "title": "Building a better publishing workflow",
      "content_state": {
        "blocks": [
          { "type": "header-one", "text": "A reliable publishing workflow" },
          { "type": "unstyled", "text": "Long-form publishing should be observable." }
        ],
        "entities": []
      }
    }
  }
}

A block's type is unstyled, header-one, header-two, header-three, unordered-list-item, ordered-list-item, blockquote or atomic.

inline_style_ranges (bold, italic, strikethrough) and entity_ranges mark up a block's own text by offset and length.

An entity_ranges entry's key is the zero-based index into entities. That is how a link or an inline image attaches.

DraftJS camelCase (entityMap, inlineStyleRanges, entityRanges) is rejected.

cover.url takes a public JPG, PNG or WebP. GIF, MP4 and extensionless URLs are not accepted.

Pick a mode:

  • mode: "publish" creates the X draft and publishes it, 2 billable X calls at $0.010 each.
  • mode: "draft" stops after the draft and returns X's draft id with no public URL, for 1 call.

X's Article format reference documents the shape in full.

If it fails

A 207 on POST /v1/posts with publishNow: true means the post was saved but X rejected it.

post.status is failed, and platforms[].errorMessage names the cause. The most common one is the character limit:

{
  "message": "Post created but publishing failed",
  "error": "All platforms failed",
  "post": {
    "_id": "65f1c0a9e2b5af0012ab34cd",
    "status": "failed",
    "platforms": [
      {
        "platform": "twitter",
        "status": "failed",
        "errorMessage": "Tweet text is too long (312 characters). Twitter's limit is 280 characters. Note: URLs count as 23 characters."
      }
    ]
  }
}

Shorten content. When the same post goes to platforms with higher limits, set customContent on the X entry instead.

207 is a 2xx status, so fetch(...).ok is true. Branch on the status code and on post.status.

Error handling covers the envelope, and Limits & Errors lists the other messages.

Related

  • Replies & Quotes: replyToTweetId and quoteTweetId.
  • Fields, Geo & Polls: every other platformSpecificData option.
  • Media & Video: the limits behind Step 2.
  • Post lifecycle: statuses and what you can do in each.
  • Idempotency: safe retries with x-request-id.
Was this page helpful?

X

Publish posts, threads, replies, quotes and polls to X with the Zernio API, then read analytics, retweet, bookmark, follow and answer DMs from the same account.

Replies & quotes

Reply to a post on X with replyToTweetId, reply with a whole thread, and quote a post with quoteTweetId.

On this page

Before you startStep 1: create a postStep 2: attach mediaStep 3: publish a threadStep 4: edit a published postPublish an ArticleIf it failsRelated