How to Publish X Articles via API
(content_state Explained)
X opened Articles to the API in 2026. This guide covers both ways to publish long-form programmatically: the raw X endpoints with their content_state block format, and the one-call markdown route through OpenTweet.
7-day free trial • Cancel anytime
Before You Write Any Code
One requirement applies to every path: the X account that publishes the article needs an X Premium subscription. Any Premium tier works. Premium+ has not been required since January 2026, so ignore older docs and blog posts that still say otherwise. Without Premium, the publish call fails no matter how correct your payload is.
Everything else depends on the path you pick. The raw X route additionally needs an X developer account with pay-per-use API billing (the default for new developers since February 2026) and an OAuth 2.0 user-context token for the publishing account. The OpenTweet route needs an OpenTweet API key and a connected X account, and no developer account at all.
Part 1: The Raw X Articles API
X's v2 API exposes Articles as a two-step flow: create a draft, then publish it. The draft endpoint takes a title and a content_state object describing the article body.
# 1. Create the draft (title + content_state)
curl -X POST https://api.x.com/2/articles/draft \
-H "Authorization: Bearer $X_USER_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d @article.json
# => { "data": { "id": "1815550000000000000" } }
# 2. Publish the draft
curl -X POST https://api.x.com/2/articles/1815550000000000000/publish \
-H "Authorization: Bearer $X_USER_ACCESS_TOKEN"
# => { "data": { "post_id": "1815550000000000001" } }The content_state Format
content_state is a DraftJS-style rich text structure with two arrays: blocks (one entry per paragraph, heading, list item, quote, or media slot) and entities (links, images, and embedded posts referenced by blocks). Bold, italic, and strikethrough are expressed as inline_style_ranges with character offsets. Media lives in atomic blocks whose entity_ranges point at an entity.
Here is a complete draft payload for a short article with a heading, a bold phrase, and one image:
{
"title": "Why We Ship Weekly",
"content_state": {
"blocks": [
{ "text": "Why we ship weekly", "type": "header-one" },
{
"text": "Shipping every week keeps the feedback loop short.",
"type": "unstyled",
"inline_style_ranges": [
{ "offset": 9, "length": 10, "style": "bold" }
]
},
{
"text": " ",
"type": "atomic",
"entity_ranges": [{ "key": 0, "offset": 0, "length": 1 }]
}
],
"entities": [
{
"key": "0",
"value": {
"type": "image",
"mutability": "immutable",
"data": {
"media_items": [
{ "media_category": "tweet_image", "media_id": "18112345678901234" }
],
"caption": "Our release dashboard"
}
}
}
]
}
}The Gotchas Nobody Documents
No code-block type exists
The supported block types are unstyled, header-one/two/three, unordered-list-item, ordered-list-item, blockquote, and atomic. There is no code block. Fenced code degrades to plain unstyled text, so keep code short or link to a gist.
Images must be tweet_image
Article media_items only accept the tweet_image media category. Upload each image through the media endpoint first, then reference the media_id from an image entity. GIFs and videos are rejected.
It is a two-step flow
There is no single publish call. You POST the draft, read data.id from the response, then POST /2/articles/{id}/publish. The publish response gives you data.post_id, the seed post of the article, not a URL.
snake_case, not DraftJS camelCase
content_state looks like DraftJS raw state, but the wire format is snake_case: inline_style_ranges, entity_ranges, from_index. Serializing actual DraftJS output without renaming fields will fail.
Mentions and hashtags need block data
For @mentions, #hashtags, and $cashtags to render as live links, each block needs data spans with from_index and to_index for every occurrence. Plain text in the block is not enough.
No scheduling on X's side
A draft sits on X until something calls publish. If you want an article to go out at 9am Tuesday, you need your own scheduler infrastructure, or a tool that runs one for you.
None of this is impossible to build. It is just a lot of plumbing for "post my markdown as an article": a markdown parser, offset math for style ranges, a media upload pipeline, entity bookkeeping, and your own scheduler. That plumbing is exactly what Part 2 replaces.
Part 2: Markdown In, One API Call
OpenTweet wraps the whole flow behind one endpoint. You send a title and a markdown body with your ot_ API key; it converts the markdown to content_state, uploads every image, creates the draft on X, and publishes it, either immediately or at a scheduled time. As of July 2026 it is among the first X schedulers to expose Articles over a public API and MCP.
curl -X POST https://opentweet.io/api/v1/articles \
-H "Authorization: Bearer ot_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"title": "Why We Ship Weekly",
"content_markdown": "# Why we ship weekly\n\nShipping **every week** keeps the feedback loop short.\n\n",
"scheduled_date": "2026-07-15T14:00:00Z"
}'The response is the stored article, ready to publish at the scheduled time:
{
"article": {
"id": "686d2f9ab1c4e8d92f1a7c03",
"title": "Why We Ship Weekly",
"content_markdown": "# Why we ship weekly\n\nShipping **every week**...",
"cover_image_url": null,
"status": "scheduled",
"scheduled_date": "2026-07-15T14:00:00.000Z",
"published_date": null,
"x_article_id": null,
"x_post_id": null,
"article_url": null,
"failed_reason": null,
"x_account_id": null,
"created_at": "2026-07-09T10:12:44.000Z",
"updated_at": "2026-07-09T10:12:44.000Z"
}
}To skip scheduling, pass publish_now: true (mutually exclusive with scheduled_date), or publish any stored article by ID:
# Publish immediately instead of scheduling
curl -X POST https://opentweet.io/api/v1/articles \
-H "Authorization: Bearer ot_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"title": "Why We Ship Weekly",
"content_markdown": "# Why we ship weekly\n\n...",
"publish_now": true
}'
# Or publish an existing draft or scheduled article by ID
curl -X POST https://opentweet.io/api/v1/articles/686d2f9ab1c4e8d92f1a7c03/publish \
-H "Authorization: Bearer ot_your_api_key"
# => article.status becomes "published" and article_url
# points at the live post on XAll Article Endpoints
| Method | Endpoint | What it does |
|---|---|---|
| POST | /api/v1/articles | Create an article: draft, scheduled (scheduled_date), or published immediately (publish_now) |
| GET | /api/v1/articles | List articles with page, limit, status, and x_account_id filters |
| GET | /api/v1/articles/:id | Get one article including its full markdown |
| PUT | /api/v1/articles/:id | Update a draft, scheduled, or failed article; set scheduled_date to null to unschedule |
| POST | /api/v1/articles/:id/publish | Publish a draft, scheduled, or failed article to X now |
| DELETE | /api/v1/articles/:id | Delete an article from OpenTweet (does not remove published articles from X) |
Article status moves through draft, scheduled, publishing, published, and failed. A failed publish returns X's exact error as failed_reason (Premium missing on the account is the most common one), and a PUT on a failed article resets it so you can try again. Multi-account users can target a specific account with x_account_id. Full reference in the API docs.
What the One Call Does for You
- Markdown to content_state conversion: headings, bold, italic, strikethrough, links, lists, and quotes map to the right block types and inline_style_ranges
- Image handling: every  in your markdown is uploaded to X as tweet_image and wired into an atomic block; failed uploads fall back to plain links instead of dropping content
- Embedded posts: an x.com status URL on its own line becomes an embedded post entity
- Mention, hashtag, and cashtag spans in block data so they render as live links
- The draft-then-publish flow, OAuth token refresh, and cover image upload
- Scheduling: pass scheduled_date and a scheduler publishes at that time; failures are stored with X's exact error as failed_reason so you can fix and re-publish
One honest limit carries over from X: since content_state has no code-block type, fenced code in your markdown is preserved as plain text, not formatted code. Articles require a paid OpenTweet plan (not available during the trial). See pricing.
Publish Articles From an AI Agent (MCP)
The same endpoints power the OpenTweet MCP server, so an agent can turn a blog post or changelog into a scheduled X Article without you writing any glue code. Point any MCP client at the hosted endpoint, no local install needed:
{
"mcpServers": {
"opentweet": {
"type": "streamable-http",
"url": "https://mcp.opentweet.io/mcp",
"headers": { "Authorization": "Bearer ot_your_key" }
}
}
}Six article tools ship alongside the tweet and scheduling tools: opentweet_create_article, opentweet_list_articles, opentweet_get_article, opentweet_update_article, opentweet_publish_article, and opentweet_delete_article. The create and update tools take the same markdown as the REST API. Setup details for Claude, Cursor, and other clients are in the MCP docs.
Tips for Programmatic Articles
Draft First, Publish After Review
Create with no scheduled_date to store a draft, review it in the OpenTweet editor or via GET, then hit the publish endpoint. Nothing goes live until you say so.
Embed Your Own Posts
Put an x.com status URL on its own line in the markdown and it becomes an embedded post inside the article. Great for turning a thread into the article's spine.
Pipe Your Blog Into It
If your blog is already markdown, a publish hook that POSTs the same file to /api/v1/articles gives you an X-native version of every post with zero extra writing.
Check failed_reason on Failures
A publish failure returns 502 with code publish_failed and the article object. failed_reason carries X's exact error message, so you can tell a Premium problem from a payload problem instantly.
Frequently Asked Questions
Does X have an API for Articles?
Yes. X opened Articles endpoints in its v2 API in 2026. You create a draft with POST /2/articles/draft, passing a title and a content_state object, then publish it with POST /2/articles/{id}/publish. It requires an X developer account and X Premium on the publishing account.
What is content_state in the X Articles API?
content_state is X's rich-text format for article bodies: an array of blocks (paragraphs, headings, list items, quotes, atomic media blocks) plus an entities array for links, images, and embedded posts. It resembles DraftJS raw state but uses snake_case field names like inline_style_ranges and entity_ranges.
Do X Articles require Premium+ to publish via API?
No. Publishing X Articles requires X Premium on the publishing account, and since January 2026 any Premium tier works. Premium+ was only required before that change.
Can I put code blocks in an X Article?
Not as formatted code. The content_state format has no code-block block type, so fenced code degrades to plain unstyled text blocks. Plan around it: keep code short or link out to a gist.
How do images work in the X Articles API?
Each image must first be uploaded through X's media upload endpoint, and article media_items only accept the tweet_image media category. GIFs and videos are rejected. The resulting media_id goes into an image entity referenced by an atomic block.
Is there a simpler way to publish X Articles programmatically?
Yes. OpenTweet exposes X Articles over a REST API and MCP: POST /api/v1/articles with a title and a markdown body, and it handles the content_state conversion, image uploads, and the draft-then-publish flow. No X developer account is needed.
Can AI agents publish X Articles?
Yes. The OpenTweet MCP server includes article tools (opentweet_create_article, opentweet_publish_article, and more), so Claude and other MCP clients can draft, schedule, and publish long-form articles. The hosted endpoint is https://mcp.opentweet.io/mcp.
Can I schedule an X Article instead of publishing immediately?
X's own API has no article scheduling; a draft sits until you call publish. With OpenTweet you pass scheduled_date on POST /api/v1/articles and it publishes automatically at that time.
Related Guides
X Articles Feature Overview
What OpenTweet Articles look like in the editor: markdown, live preview, cover images, and scheduling.
How to Schedule X Articles
The no-code walkthrough: write in markdown, pick a time, and let OpenTweet publish.
API Reference
Authentication, rate limits, and every endpoint including articles, posts, and media.
Post to Twitter From Your API
The companion guide for regular tweets and threads over REST.
MCP Setup
Connect Claude, Cursor, or any MCP client to the hosted OpenTweet server.
X API Pay-Per-Use Explained
What the 2026 X API pricing change means if you build on the raw API.
Ship Your First Article From Code Today
One POST request with markdown. No content_state, no media pipeline, no developer account.
Get Your API Key