Get Article API | Read a Published or Draft X Article
Read an article's full content. Two mutually exclusive forms: id or url (the announcement tweet id or status URL) for a public, pooled read that needs no session, works for Published articles only. Or article_id (the article's own entity id, from Article Create or Article List) for an owner-only read that requires a registered session and also works for your own Drafts. Billed at $0.0008 per call. Cost: $0.0008 per call.
Get Article reads an article's full content (title, content_state, cover media, author). Two mutually exclusive forms: id or url (the announcement tweet) for a public read of any Published article, needing only the API key; or article_id (the article's own entity id) for an owner-only read that requires a registered session and also reaches your own Drafts. It returns 404 when not found, not visible, or not owned by the caller.
Authorization
bearerAuth Pass your API key as a bearer token on every request: Authorization: Bearer <API_KEY>.
In: header
Query Parameters
PUBLIC form. The article's announcement tweet id, or its status URL. Provide exactly one of id, url, or article_id.
PUBLIC form. Status URL of the article's announcement tweet, for example https://x.com/i/status/2086650300012345678. Provide exactly one of id, url, or article_id.
OWNER-ONLY form. The article's own entity id, from Article Create or Article List. Requires a registered session. Provide exactly one of id, url, or article_id.
Comma-separated list of dotted field paths to keep in the response (one parameter, never repeated), applied to every object in the returned lists and to nested objects (for example fields=id,text,author.username; the list's own name may prefix a path, fields=tweets.id). A path prefix that lands on an array applies to each element (media.media_url_https). Pagination and envelope keys (next_cursor, cursor, has_more, count, partial, error, message, reason) are always kept. Unknown paths are ignored; up to 50 paths of up to 6 segments. Projection never changes status codes or billing, and applies to data reads only: account, feedback, session status, monitor (other than deliveries), webhook and stream-compat reads return their bodies unchanged.
Set to 1 (or true) to apply the built-in compact preset: ids, url, text, created_at, lang, the engagement counts, the is_retweet/is_reply/is_quote flags, conversation ids, the author's id/username/name/followers_count/verification, and the quoted or retweeted tweet's id/url/author username. The preset trims what it recognises and leaves alone what it would empty, so it never turns a body into {}. Combine with fields to add paths on top of the preset. Typically cuts a tweet page by 70 to 90 percent for LLM tool callers.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/article/get?id=2086650300012345678&url=https%3A%2F%2Fx.com%2Fi%2Fstatus%2F2086650300012345678&article_id=QXJ0aWNsZUVudGl0eToyMDg2NjUwMjI4OTUwNjgzNjQ4&fields=id%2Ctext%2Ccreated_at%2Cauthor.username&compact=1"{ "id": "2086650300012345678", "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
| Unit | Price |
|---|---|
| Per call | $0.0008 |
| Per 1,000 calls | $0.80 |
When to use
Use the id/url form to read any published article you have the announcement tweet for, including ones you do not own. Use the article_id form when you already hold an entity id from Article Create or Article List and want to read that specific article again, including a Draft. To enumerate your own articles from scratch instead of reading one you already have an id for, use Article List.
Notes
- PUBLIC form (id/url): no customer session or per-call inline credentials needed, just the API key. 404 ("No article found at that tweet id/URL") covers three cases alike: the tweet was deleted, the account is protected, or the tweet exists but is not an article announcement. Only reaches PUBLISHED articles.
- OWNER-ONLY form (article_id): requires a registered session (POST /customer/session) or per-call inline credentials. Also reaches your own Drafts, which have no announcement tweet the public form could resolve. No dedicated get-by-id operation exists upstream, so this scans your own Draft then Published lists and matches on the id you provided, the same lookup Article Delete already does internally.
Related endpoints
- List Articles: List your own articles, filtered by lifecycle (draft or published).
- Create Article: Start a new draft article. No request body required.
- Publish Article: Publish a draft article. This posts a real, public announcement tweet.
- Tweet Detail: Fetch a single tweet's full object by ID.
FAQ
Do I need a registered session to read an article?
Only for the article_id form. The id/url form is a public pooled read, exactly like Tweet Detail: it only needs your API key. The article_id form is owner-only and requires a registered session or per-call inline credentials, the same as Article List.
What happens if the tweet is not an article announcement, or the article_id is not found?
Either way you get a 404 and article comes back null rather than an error body. For id/url that covers the tweet being deleted, protected, or not an article announcement. For article_id it covers the id not existing or not belonging to the calling account, the same as twitterapi.io's contract, which does not distinguish the two either.
Can I read my own draft this way?
Yes, with the article_id form, since it is owner-only and does not depend on an announcement tweet (a draft has none). The id/url form cannot reach a draft for the same reason: there is no public tweet to resolve.
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.
List Articles GET
List the caller's own articles at one lifecycle at a time: Draft or Published (default Draft). X exposes no combined view across both, so call it twice, once per lifecycle, to see everything. Cursor-paginated. Requires a registered session. Billed at $0.0008 per call. Cost: $0.0008 per call.