← All platforms

X publishing API

Live

Post text, images, video or GIFs to X. Billed per post from prepaid credit rather than your monthly quota, because X charges per call.

Connect an account and publish today.

What this channel accepts

MediaOptional, up to 4 items
Text280 weighted characters
Option blockNone
Billing$0.02, or $0.25 with a link
Account refx:<user-id>

Publish to X

One request shape, the same across every channel. Register media first with POST /v1/media, then pass the handles it returns.

POST /v1/posts returns 202 with a post id; the publish itself is asynchronous.

const res = await fetch("https://creasionapp.com/v1/posts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CREASION_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "accounts": [
      "x:1895015660515897344"
    ],
    "content": "Shipped: media upload, chunked the way X requires.",
    "media": [
      "media_01JQ8ZR4X7",
      "media_01JQ8ZR4X8"
    ]
  }),
});

// 202 Accepted: the post is queued. Poll GET /v1/posts/{id}
// or wait for the post.completed webhook.
const { id } = await res.json();

Before you integrate

A link changes the price

X charges more for posts containing a link, so a post with one costs $0.25 against $0.02 without. The price is returned before you publish, never after.

Weighted characters, not characters

A link always counts as 23 regardless of length, and emoji count as 2. We measure the same way X does, so a post that fits here fits there.

Knowing what happened

Publishing is asynchronous, so the create call returns before the post exists on X. Poll GET /v1/posts/{id} or register a webhook and receive post.completed instead. When a post targets several accounts, each destination reports its own result, so a partial success names exactly which one failed and why.

Read the API docsGet an API key