TwitterAPIs Docs
API ReferenceArticles

Publish Article API

Publish a draft article as the account behind your registered session, transitioning it from Draft to Published. WARNING: publishing posts a real, public announcement tweet on the account's timeline. This is not a preview or a private action, and it cannot be undone by re-editing the article: once published, the only way back is Delete Article, which unpublishes the article AND removes the announcement tweet. Requires a registered session. Billed at $0.0016 per call. Cost: $0.0016 per call.

POST article/publish moves a draft Twitter/X article from Draft to Published status. It posts a real, public announcement tweet linking the article to the account's timeline, an action that cannot be reversed by editing again. Requires a registered session and the article's id. Returns ok (boolean) and an article object containing tweet_id and public_url. Costs $0.0016 per call.

POST
/article/publish

Authorization

bearerAuth
AuthorizationBearer <token>

Pass your API key as a bearer token on every request: Authorization: Bearer <API_KEY>.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/article/publish" \  -H "Content-Type: application/json" \  -d '{    "id": "QXJ0aWNsZUVudGl0eToyMDg2NjUwMjI4OTUwNjgzNjQ4"  }'
{  "ok": true,  "article": {    "id": "QXJ0aWNsZUVudGl0eToyMDg2NjUwMjI4OTUwNjgzNjQ4",    "rest_id": "2086650228950683648",    "title": "Why we rebuilt our onboarding from scratch",    "preview_text": "Opening paragraph.",    "lifecycle": "Published",    "content_state": {      "blocks": [        {          "key": "a1b2c",          "text": "Opening paragraph.",          "type": "unstyled",          "inlineStyleRanges": [],          "entityRanges": []        }      ],      "entityMap": []    },    "cover_media": null,    "media_entities": [],    "author": {      "id": "44196397",      "username": "elonmusk",      "name": "Elon Musk"    },    "created_at_secs": 1786400000,    "modified_at_secs": 1786400400,    "first_published_at_secs": 1786400400,    "visibility_setting": "Public",    "tweet_id": "2086650300012345678",    "public_url": "https://x.com/i/article/2086650228950683648"  }}
{  "error": "bad_request",  "message": "Missing or malformed parameter. Fix the request before retrying."}
{  "error": "unauthorized",  "message": "The API key is missing, malformed, or revoked. Check the Authorization header."}
{  "error": "insufficient_credits",  "message": "Your balance is exhausted. Top up your credits to continue."}
{  "error": "forbidden",  "message": "X refused this resource for the acting account: on a read, the account is not a participant in the DM thread, the target is protected or has blocked it, or the session was attestation-blocked for calling from a datacenter or rotating IP; on a write, the account is not authorized for the action or has no logged-in session. The credentials are still valid, so do not re-register."}
{  "error": "not_found",  "message": "The resource does not exist, for example a deleted tweet or a private account."}
{  "error": "rate_limited",  "message": "Too many requests. Back off and retry with exponential backoff."}
{  "error": "server_error",  "message": "Something failed on our side. Retry with backoff; if it persists, contact support."}

Pricing

UnitPrice
Per call$0.0016
Per 1,000 calls$1.60

When to use

Use this endpoint when a draft article created via article/create is ready to go live and you want to transition it from Draft to Published, triggering the public announcement tweet. Reach for the sibling article/update_content or article/update_title endpoints instead if you still need to edit the draft before publishing, since publish is a one-way action.

Notes

  • WARNING: this posts a real, public announcement tweet on the acting account's timeline. It is not reversible by editing the article again; the only reversal is Delete Article (which unpublishes and removes the announcement tweet) or Unpublish Article (which reverts to Draft but leaves the announcement tweet in place).
  • [VERIFY] Request variable names and the audience/reply_control defaults are inferred from a captured X dialog default, not a captured request; not executed live end-to-end.
  • Needs a session for the account you are publishing as: either register one once (POST /customer/session), or pass per-call inline credentials as x-auth-token and x-ct0 request headers, which let a single API key act as many accounts. Returns 409 session_required if neither is supplied, 401 session_dead when the session has expired.
  • caption is capped at 256 characters by X; longer captions are rejected upstream, not truncated by us.
  • Unpublish Article: Revert a published article back to Draft. The announcement tweet is left in place.
  • Delete Article: Delete an article. A draft is hard-deleted; a published article is unpublished, then its announcement tweet is removed too.
  • Get Article: Read an article via its announcement tweet, or your own via its entity id.
  • Update Article Content: Replace an article's body with a new Draft.js content_state.

FAQ

Does publishing an article delete or replace the announcement tweet if I edit the article afterward?

No. Editing the published article's content does not update or remove the original announcement tweet. The endpoint description states publishing cannot be undone by re-editing, so the tweet stays live on the timeline exactly as it was posted even if the article content changes later.

How do I control who can see the article and who can reply to the announcement tweet?

Use the audience parameter to set who can read the published article and reply_control to set who can reply to the announcement tweet; both default to Everyone if omitted. These are independent settings, so you can restrict article readership without restricting replies, or vice versa.

Update Article Content POST

Replace the body of an article you own with a new content_state, identified by its entity id. content_state is Draft.js editor-state JSON ({ blocks: [...], entityMap: [...] }) that you build client-side; in the shape the X Article editor produces. Blocks are forwarded with only key, text, type, data, entityRanges and inlineStyleRanges; X's schema rejects any other block field (depth included). Block types: unstyled, header-two, unordered-list-item, ordered-list-item, blockquote and atomic (a one-space block that carries an entity through entityRanges). entityMap is an ARRAY of { key, value: { type, mutability, data } }; entity data is snake_case on input and comes back camelCase: TWEET (an embedded post) { tweet_id }, MEDIA (an inline image) { caption, entity_key, media_items: [{ local_media_id, media_category: "DraftTweetImage", media_id }] } with media_id from media/upload, DIVIDER {}, LINK { url } over a text range, MARKDOWN { markdown } for tables. A field X does not know is refused with 422, reason validation_failed and the offending path in detail. Measured live 2026-09-18. Requires a registered session. Billed at $0.0016 per call. Cost: $0.0016 per call.

Unpublish Article POST

Revert a Published article back to Draft, identified by its entity id. The announcement tweet the original publish posted is left in place; use Delete Article to remove it too. X server-validates this transition and returns a 422 (invalid_lifecycle) if the article is not currently Published. Requires a registered session. Billed at $0.0016 per call. Cost: $0.0016 per call.