Publish Markdown to X Articles with TwexAPI
Many content teams do not write directly in X. Their source files live in GitHub, Notion exports, CMS draft libraries, or internal knowledge bases.
The hard part is not reading a .md file. It is publishing that file as an X Article without manual copy-paste, without losing images, and without starting over when one step fails.
TwexAPI Article endpoints fit this scenario when you treat publishing as a stateful workflow. They split the job into five retriable steps: create a draft and get article_id; optionally set a cover; set title and Markdown body; review the preview; then publish the draft and get tweet_id.
Use Case: Publish from a Content Repository
Suppose you have a content publishing pipeline:
- Editors maintain
article.mdin a repository - CI, background jobs, or ops tools read the Markdown file
- The system syncs body, title, and cover to an X Article
- After publish, the returned
tweet_idis saved back to a database or CMS
What matters is not only “can we publish?” but “can we recover safely on failure.” For example, a failed cover upload should not discard the whole article; bad body formatting should allow re-setting content; before publish, you should be able to open X’s preview link to review the draft.
That is why this guide starts with the step-by-step flow. A one-shot publish endpoint is useful for simple jobs, but it gives you less room to inspect and recover.
API Flow Overview
TwexAPI's five-step flow:
| Step | Method | Endpoint | Purpose |
|---|---|---|---|
| 1 | POST | /x/articles/draft | Create an empty draft; returns article_id and preview_url |
| 2 | PUT | /x/articles/{article_id}/cover | Optional: upload and set article cover |
| 3 | PUT | /x/articles/{article_id}/title | Set article title (plain text) |
| 4 | PUT | /x/articles/{article_id}/content | Set article body (Markdown) |
| 5 | POST | /x/articles/{article_id}/publish | Publish draft; returns article_id and tweet_id |
All requests require a TwexAPI Bearer Token. The request body must include the X account cookie, typically auth_token, ct0, and twid.
export TWEXAPI_TOKEN="your_twexapi_bearer_token"
export X_COOKIE="auth_token=...; ct0=...; twid=..."Do not commit
X_COOKIEto your repo, logs, or frontend code. Keep it only in server environment variables or a secrets manager, and rotate it if it is ever exposed.
Step 1: Create an X Article Draft
Create an empty draft first. On success, data.article_id is used for all later steps; data.preview_url is for manual preview.
curl --request POST \
--url https://api.twexapi.io/x/articles/draft \
--header "Authorization: Bearer $TWEXAPI_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"cookie": "auth_token=...; ct0=...; twid=..."
}'A successful response includes:
{
"code": 200,
"msg": "success",
"data": {
"article_id": "article-draft-id",
"preview_url": "https://x.com/compose/articles/edit/article-draft-id"
}
}In automation, save article_id immediately. If setting title or body fails later, retry with the same article_id instead of creating a new draft.
Step 2: Set Cover Image (Optional)
If your Markdown article has a separate cover, set it before publish. cover_image supports https:// image URLs or a local file path per the API docs.
curl --request PUT \
--url https://api.twexapi.io/x/articles/article-draft-id/cover \
--header "Authorization: Bearer $TWEXAPI_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"cookie": "auth_token=...; ct0=...; twid=...",
"cover_image": "https://example.com/cover.png"
}'On success you get the uploaded X media_id:
{
"code": 200,
"msg": "success",
"data": {
"media_id": "1234567890"
}
}Skip this step if you have no cover.
Step 3: Set Article Title
The title is plain text, not Markdown. Extract it from Markdown frontmatter, the first # Heading, or a CMS field.
curl --request PUT \
--url https://api.twexapi.io/x/articles/article-draft-id/title \
--header "Authorization: Bearer $TWEXAPI_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"cookie": "auth_token=...; ct0=...; twid=...",
"title": "How We Built a Weekly AI Research Digest"
}'On success this step usually returns only code and msg, with data as null.
Step 4: Write Markdown Body to the Article
Pass the body via the markdown field. Per the Article Content API, Markdown supports headings, bold, italic, lists, code blocks, and inline images, e.g.:
# How We Built a Weekly AI Research Digest
Every Friday, our crawler collects public X posts from selected AI builders.
## Workflow
- Collect tweets from a curated account list
- Summarize recurring themes
- Publish the digest as an X Article
Inline images are uploaded to X automatically and embedded in the article.
curl --request PUT \
--url https://api.twexapi.io/x/articles/article-draft-id/content \
--header "Authorization: Bearer $TWEXAPI_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"cookie": "auth_token=...; ct0=...; twid=...",
"markdown": "# How We Built a Weekly AI Research Digest\n\nEvery Friday..."
}'A successful response returns the parsed block count:
{
"code": 200,
"msg": "success",
"data": {
"blocks": 8
}
}If blocks is much lower than expected, check Markdown structure or image URLs. Pause publishing and retry Step 4 first.
Step 5: Publish the Draft
After title and body are set, call publish. visibility is optional; default is Public; you can also use Followers or Mentioned.
curl --request POST \
--url https://api.twexapi.io/x/articles/article-draft-id/publish \
--header "Authorization: Bearer $TWEXAPI_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"cookie": "auth_token=...; ct0=...; twid=...",
"visibility": "Public"
}'On success:
{
"code": 200,
"msg": "success",
"data": {
"article_id": "article-draft-id",
"tweet_id": "1880000000000000000"
}
}tweet_id is the X post ID for this X Article. Store it in your content system for display, analytics, comment monitoring, or redistribution.
Confirm Step 3 and Step 4 are done before publish. The API allows publishing drafts without title or body, but such articles will appear empty on X.
A Runnable Node.js Script
The script below reads a local Markdown file, then creates a draft, sets cover, title, and body. By default it stops at the preview step; set PUBLISH_ARTICLE=true only after the draft has passed your review. That keeps a CI job or local test from accidentally publishing unfinished content.
1import { readFile } from "node:fs/promises";
2
3const API_BASE = "https://api.twexapi.io";
4const token = process.env.TWEXAPI_TOKEN;
5const cookie = process.env.X_COOKIE;
6const markdownPath = process.argv[2] || "article.md";
7
8if (!token || !cookie) {
9 throw new Error("Missing TWEXAPI_TOKEN or X_COOKIE environment variable.");
10}
11
12async function request(path, method, body) {
13 const response = await fetch(`${API_BASE}${path}`, {
14 method,
15 headers: {
16 Authorization: `Bearer ${token}`,
17 "Content-Type": "application/json",
18 },
19 body: JSON.stringify(body),
20 });
21
22 const data = await response.json();
23
24 if (!response.ok || data.code !== 200) {
25 throw new Error(`${method} ${path} failed: ${JSON.stringify(data)}`);
26 }
27
28 return data.data;
29}
30
31function inferTitle(markdown) {
32 const h1 = markdown.match(/^#\s+(.+)$/m);
33 return h1 ? h1[1].trim() : "Untitled X Article";
34}
35
36const markdown = await readFile(markdownPath, "utf8");
37const title = process.env.ARTICLE_TITLE || inferTitle(markdown);
38const coverImage = process.env.COVER_IMAGE;
39const visibility = process.env.ARTICLE_VISIBILITY || "Public";
40const shouldPublish = process.env.PUBLISH_ARTICLE === "true";
41
42const draft = await request("/x/articles/draft", "POST", { cookie });
43console.log("Draft created:", draft.article_id);
44console.log("Preview URL:", draft.preview_url);
45
46if (coverImage) {
47 const cover = await request(`/x/articles/${draft.article_id}/cover`, "PUT", {
48 cookie,
49 cover_image: coverImage,
50 });
51 console.log("Cover uploaded:", cover.media_id);
52}
53
54await request(`/x/articles/${draft.article_id}/title`, "PUT", {
55 cookie,
56 title,
57});
58console.log("Title set:", title);
59
60const content = await request(`/x/articles/${draft.article_id}/content`, "PUT", {
61 cookie,
62 markdown,
63});
64console.log("Markdown parsed into blocks:", content.blocks);
65
66if (!shouldPublish) {
67 console.log("Draft ready for review. Set PUBLISH_ARTICLE=true after checking the preview URL.");
68 process.exit(0);
69}
70
71const published = await request(`/x/articles/${draft.article_id}/publish`, "POST", {
72 cookie,
73 visibility,
74});
75
76console.log("Article published:", published);Run it:
TWEXAPI_TOKEN="your_token" \
X_COOKIE="auth_token=...; ct0=...; twid=..." \
COVER_IMAGE="https://example.com/cover.png" \
ARTICLE_VISIBILITY="Public" \
node publish-x-article.mjs ./article.mdOmit COVER_IMAGE if you do not need a cover. Add PUBLISH_ARTICLE=true only when the preview is approved.
Production Notes
When publishing Markdown to X Article, treat each step as a recoverable task:
- Save
article_id: retry later steps on the same draft if something fails. - Save
preview_url: let editors or ops review before publish. - Do not log full cookies: at most log whether a cookie is present, never the value.
- Check
blocksafter Step 4: pause publish if the parsed block count looks wrong. - Make publish explicit: require a confirmation flag or approval state before Step 5 runs.
- Save
tweet_idafter publish: use it for comment monitoring, performance analysis, or archiving.
When to Use One-Step Publish
For a quick Markdown publish only, use POST /x/articles/publish with cookie, title, markdown, and optional cover_image and visibility in one call.
For content platforms, automation pipelines, preview needs, or failure retries, the five-step flow is safer. You know which step failed, can fix cover, title, or body without creating a new article, and can require human approval before the final publish call.
Summary
Publishing Markdown to X Article is straightforward when the workflow is explicit: create a draft and get article_id; optionally set cover; set a plain-text title; write .md content as markdown; review the preview; then publish and save tweet_id.
This flow fits integrating X Article into existing content systems. Editors keep writing in Markdown; developers turn it into a stable, traceable, retriable publish pipeline.