> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xquik.com/llms.txt
> Use this file to discover all available pages before exploring further.

# X article API for long-form tweet & post content

> Retrieve one long-form X Article by tweet ID with title, body blocks, cover image, author profile, publication time, and engagement metrics. 5 credits per call.

<Panel>
  <Tabs defaultTabIndex={0} sync={false}>
    <Tab title="200" id="response-x-get-article-200">
      ```json theme={null}
      {
        "article": {
          "articleId": "2100651802404544512",
          "title": "The Future of AI",
          "previewText": "A deep dive into the latest AI trends...",
          "coverImageUrl": "https://pbs.twimg.com/media/example.jpg",
          "contents": [
            {
              "type": "paragraph",
              "text": "This is the first paragraph of the article.",
              "links": [
                {
                  "offset": 18,
                  "length": 9,
                  "url": "https://example.com/paragraph"
                }
              ]
            }
          ]
        },
        "author": {
          "id": "9876543210",
          "name": "Elon Musk",
          "username": "elonmusk",
          "profilePicture": "https://pbs.twimg.com/profile_images/example.jpg"
        }
      }
      ```
    </Tab>

    <Tab title="400" id="response-x-get-article-400">
      ```json theme={null}
      {
        "error": "invalid_input",
        "message": "Invalid input. Check the request body."
      }
      ```
    </Tab>

    <Tab title="401" id="response-x-get-article-401">
      ```json theme={null}
      {
        "error": "unauthenticated",
        "message": "Authentication required. Provide a valid API key or bearer token."
      }
      ```
    </Tab>

    <Tab title="402" id="response-x-get-article-402">
      ```json theme={null}
      {
        "error": "insufficient_credits",
        "message": "Insufficient credits. Top up or subscribe to continue."
      }
      ```
    </Tab>

    <Tab title="404" id="response-x-get-article-404">
      ```json theme={null}
      {
        "error": "article_not_found",
        "message": "Article not found. Use an X Article tweet ID."
      }
      ```
    </Tab>

    <Tab title="424" id="response-x-get-article-424">
      ```json theme={null}
      {
        "error": "x_api_unavailable",
        "message": "X data source temporarily unavailable. Try again later."
      }
      ```
    </Tab>

    <Tab title="429" id="response-x-get-article-429">
      ```json theme={null}
      {
        "error": "rate_limit_exceeded",
        "message": "Too many requests. Try again later.",
        "retryAfter": 60
      }
      ```
    </Tab>

    <Tab title="502" id="response-x-get-article-502">
      ```json theme={null}
      {
        "error": "x_api_unavailable",
        "message": "X data source temporarily unavailable. Try again later."
      }
      ```
    </Tab>

    <Tab title="503" id="response-x-get-article-503">
      ```json theme={null}
      {
        "error": "x_api_unavailable",
        "message": "Xquik is busy right now. Retry shortly."
      }
      ```
    </Tab>
  </Tabs>
</Panel>

<blockquote className="agent-llms-directive">
  For the complete documentation index, see <a href="/llms.txt">llms.txt</a>.
</blockquote>

<Callout icon="coins" color="#5c3327">
  **5 credits per call** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Direct [MPP](/mpp/machine-payments-protocol): USD 0.00075 per call
</Callout>

<Info>
  Get X Article returns one long-form X Article by wrapper tweet ID. The endpoint is
  `GET /api/v1/x/articles/{tweetId}`.
</Info>

<CodeGroup>
  ```bash Article tweet ID theme={null}
  curl https://xquik.com/api/v1/x/articles/2033891852621840387 \
    -H "x-api-key: xq_your_api_key_here" | jq
  ```

  ```javascript Node.js theme={null}
  const tweetId = "2033891852621840387";
  const response = await fetch(`https://xquik.com/api/v1/x/articles/${tweetId}`, {
    headers: { "x-api-key": "xq_your_api_key_here" },
  });
  const data = await response.json();
  const contentBlocks = data.article.contents ?? [];
  const author = data.author ?? {};
  const bodyBlocks = contentBlocks.filter((block) => block.text);
  const mediaBlocks = contentBlocks.filter((block) => block.url);
  const formattedBlocks = contentBlocks
    .map((block, index) => ({
      index,
      type: block.type ?? null,
      inline_style_ranges: block.inlineStyleRanges ?? [],
    }))
    .filter((block) => block.inline_style_ranges.length > 0);
  const handoff = {
    tweet_id: tweetId,
    article_title: data.article.title ?? null,
    preview_text: data.article.previewText ?? null,
    author_id: author.id ?? null,
    author_username: author.username ?? null,
    author_name: author.name ?? null,
    author_profile_picture: author.profilePicture ?? null,
    created_at: data.article.createdAt ?? null,
    cover_image_url: data.article.coverImageUrl ?? null,
    body_text: bodyBlocks.map((block) => block.text).join("\n\n"),
    body_markdown: data.article.bodyMarkdown ?? null,
    block_count: contentBlocks.length,
    block_types: contentBlocks.map((block) => block.type ?? "unknown"),
    formatted_blocks: formattedBlocks,
    media_urls: mediaBlocks.map((block) => block.url),
  };
  process.stdout.write(`${JSON.stringify(handoff)}\n`);
  ```

  ```python Python theme={null}
  import json
  import requests

  tweet_id = "2033891852621840387"
  response = requests.get(
      f"https://xquik.com/api/v1/x/articles/{tweet_id}",
      headers={"x-api-key": "xq_your_api_key_here"},
  )
  data = response.json()
  content_blocks = data["article"].get("contents", [])
  body_blocks = [block for block in content_blocks if block.get("text")]
  media_blocks = [block for block in content_blocks if block.get("url")]
  formatted_blocks = [
      {
          "index": index,
          "type": block.get("type"),
          "inline_style_ranges": block.get("inlineStyleRanges", []),
      }
      for index, block in enumerate(content_blocks)
      if block.get("inlineStyleRanges")
  ]
  author = data.get("author") or {}
  handoff = {
      "tweet_id": tweet_id,
      "article_title": data["article"].get("title"),
      "preview_text": data["article"].get("previewText"),
      "author_id": author.get("id"),
      "author_username": author.get("username"),
      "author_name": author.get("name"),
      "author_profile_picture": author.get("profilePicture"),
      "created_at": data["article"].get("createdAt"),
      "cover_image_url": data["article"].get("coverImageUrl"),
      "body_text": "\n\n".join(block["text"] for block in body_blocks),
      "body_markdown": data["article"].get("bodyMarkdown"),
      "block_count": len(content_blocks),
      "block_types": [block.get("type") for block in content_blocks],
      "formatted_blocks": formatted_blocks,
      "media_urls": [block["url"] for block in media_blocks],
  }
  print(json.dumps(handoff))
  ```
</CodeGroup>

The examples build article rows, not raw lookup dumps.
Use `GET /api/v1/x/articles/{tweetId}` when a workflow needs one long-form post
body plus article metadata. Store `tweet_id`,
`article_title`, `preview_text`, `author_id`, `author_username`, `author_name`,
`author_profile_picture`, `created_at`, `cover_image_url`, `body_text`, and
`body_markdown` with the record you pass on. Store `block_count`, `block_types`, `formatted_blocks`,
and `media_urls` when your archive, article index, or agent handoff needs
block-level completeness and formatting checks.

## Find candidate articles

Use this endpoint after you have the numeric wrapper tweet ID for an X Article.
When a workflow starts from a mixed set of tweets, search first, store candidate
tweet IDs, then try the article lookup once per candidate.

<CardGroup cols={2}>
  <Card title="Search first" icon="search">
    Use [`Search tweets`](/api-reference/x/search-tweets) to find candidate
    wrapper tweets by author, keyword, URL, or visible tweet text.
  </Card>

  <Card title="Article lookup" icon="file-text">
    Call `GET /api/v1/x/articles/{tweetId}` with the candidate tweet ID when
    the workflow needs the long-form body.
  </Card>

  <Card title="Fallback route" icon="git-branch">
    If the response is `article_not_found`, store the terminal result and switch
    to [`Get tweet`](/api-reference/x/get-tweet) or
    [`Get tweet thread`](/api-reference/x/tweet-thread).
  </Card>

  <Card title="Saved export" icon="file-spreadsheet">
    Use `article_extractor` when the workflow needs an extraction job, estimate,
    or CSV, JSON, or XLSX export.
  </Card>
</CardGroup>

```json theme={null}
{
  "content_job_id": "article-archive-q2",
  "candidate_source": "GET /api/v1/x/tweets/search",
  "article_route": "GET /api/v1/x/articles/{tweetId}",
  "tweet_id": "2033891852621840387",
  "content_type": "x_article",
  "article_error": null,
  "fallback_route": "GET /api/v1/x/tweets/{id}",
  "saved_fields": ["article_title", "preview_text", "body_text", "cover_image_url"]
}
```

## Direct article handoff

Use `GET /api/v1/x/articles/{tweetId}` when you have the numeric tweet ID for an
X Article wrapper tweet and need the article body. Use the final status ID from
an X Article URL. A normal tweet ID can be valid and still return
`404 article_not_found` when it is not an X Article.

<CardGroup cols={2}>
  <Card title="Article row" icon="file-text">
    Store `article.title`, `previewText`, `coverImageUrl`, `createdAt`, metrics,
    and a derived `body_text` field.
  </Card>

  <Card title="Body blocks" icon="list-tree">
    Store `article.contents[]` when you need headings, lists, quotes, media,
    dividers, code blocks, and inline styles.
  </Card>

  <Card title="Author joins" icon="user-round">
    Store `author.id`, `username`, `name`, and `profilePicture` when returned.
  </Card>

  <Card title="Media assets" icon="image">
    Store `coverImageUrl` and media-block `url` values for article archives and
    article review.
  </Card>

  <Card title="Not an article" icon="circle-alert">
    `article_not_found` is final for that tweet ID.
    Ask for an X Article URL or use a tweet or thread endpoint.
  </Card>

  <Card title="Saved exports" icon="file-spreadsheet">
    Use `article_extractor` when you need a saved extraction job or CSV, JSON,
    or XLSX export.
  </Card>
</CardGroup>

## Store an X article archive

Keep the wrapper Tweet ID, article fields, structured content blocks,
author, cover image, and collection time. Keep `article.contents[]` when the
archive must reconstruct headings, lists, quotes, media, dividers, or code.

| Article archive column | Response source | Archive rule |
| - | - | - |
| `wrapper_tweet_id` | Requested `tweetId` | Use as the stable article lookup key. |
| `article_title` | `article.title` | Keep the returned X Article title. |
| `preview_text` | `article.previewText` | Store the short article preview when returned. |
| `body_text` | Derived from `article.contents[]` | Build searchable text and keep the structured blocks. |
| `content_blocks` | `article.contents[]` | Keep block order, type, text, styles, and media references. |
| `cover_image_url` | `article.coverImageUrl` | Keep the article cover asset with its source record. |
| `author_id` | `author.id` | Use as the stable article-author key. |
| `author_username` | `author.username` | Display the current author handle. |
| `created_at` | `article.createdAt` | Keep the article publication time. |
| `media_urls` | Media block URLs | Keep inline images or videos with their block positions. |
| `collected_at` | Integration timestamp | Audit when you retrieved the article body. |

| Article block | Text projection | Structured value to keep |
| - | - | - |
| Heading | Heading text | Heading level and inline styles |
| Paragraph | Paragraph text | Inline links, mentions, and styles |
| List | Ordered item text | List type, item order, and nesting |
| Quote | Quoted text | Quote attribution when returned |
| Media | Alt text or caption | Media URL, type, and position |
| Code | Code text | Language and formatting metadata |
| Divider | No body text | Block position in the article |

Direct article reads cost 5 credits per successful call. For MPP callers, Xquik
bills this endpoint as a fixed charge at USD 0.00075 per call.

## Path parameters

<ParamField path="tweetId" type="string" required>
  X Article post ID or URL-encoded post URL, such as `x.com/nasa/status/20`. See [path IDs](/api-reference/overview#path-ids).
  A post without an Article returns `article_not_found`.
</ParamField>

## Which article endpoint?

<CardGroup cols={2}>
  <Card title="X article body" icon="file-text">
    Use `GET /x/articles/{tweetId}` for the title, body blocks, cover image,
    metrics, and author fields of one X Article.
  </Card>

  <Card title="Single tweet" icon="message-square">
    Use [`Get tweet`](/api-reference/x/get-tweet) for one tweet's text, media,
    author, and engagement metrics.
  </Card>

  <Card title="Tweet thread" icon="list-tree">
    Use [`Get tweet thread`](/api-reference/x/tweet-thread) for conversation
    context around the article wrapper tweet.
  </Card>

  <Card title="Search tweets" icon="search">
    Use [`Search tweets`](/api-reference/x/search-tweets) to discover candidate
    article tweets by keyword, URL, author, or other filters.
  </Card>

  <Card title="Saved exports" icon="file-spreadsheet">
    Use [`Create extraction`](/api-reference/extractions/create) with
    `toolType=article_extractor` when you need a saved job or CSV, JSON, or XLSX
    export.
  </Card>

  <Card title="Article-not-found handling" icon="circle-alert">
    Do not retry the same ID after `article_not_found`. Switch to tweet lookup
    or ask for an X Article URL.
  </Card>
</CardGroup>

## Headers

<ParamField header="x-api-key" type="string">
  Full account key. Sessions and OAuth also work.
</ParamField>

<ParamField header="Authorization" type="string">
  `Bearer xq_your_guest_key_here` authenticates `paid_reads` guest keys. Direct MPP uses the `Payment ...` credential. Get it from the `WWW-Authenticate: Payment` challenge.
</ParamField>

## Response

### 200 OK

<ResponseField name="article" type="object">
  The article data.
  **Article object fields.**

  <ResponseField name="articleId" type="string">X's ID for the article, which differs from its post's ID.</ResponseField>
  <ResponseField name="title" type="string">Article title.</ResponseField>
  <ResponseField name="previewText" type="string">Short preview text of the article.</ResponseField>
  <ResponseField name="summaryText" type="string">X's summary of the article, when X has one.</ResponseField>
  <ResponseField name="coverImageUrl" type="string">Cover thumbnail image URL.</ResponseField>
  <ResponseField name="coverMedia" type="object">Public cover media metadata. X defines its fields.</ResponseField>
  <ResponseField name="metadata" type="object">Public article metadata. X defines its fields.</ResponseField>
  <ResponseField name="lifecycleState" type="object">Public lifecycle metadata. X defines its fields.</ResponseField>
  <ResponseField name="bodyText" type="string">Plain text joined from all article content blocks. Omitted if unavailable.</ResponseField>
  <ResponseField name="bodyMarkdown" type="string">Article body as Markdown, built from the content blocks. Keeps headings, lists, bold, italic, links, images & embedded posts as their x.com URLs. Holds no HTML. Omitted if unavailable.</ResponseField>

  <ResponseField name="contents" type="object[]">
    Article body as an array of content blocks.
    **Content block fields.**
    <ResponseField name="type" type="string">Block type: `paragraph`, `header-one`, `header-two`, `header-three`, `header-four`, `header-five`, `header-six`, `ordered-list-item`, `unordered-list-item`, `blockquote`, `code-block`, `markdown`, `media`, `tweet`, or `divider`.</ResponseField>
    <ResponseField name="text" type="string">Text content for text-based blocks.</ResponseField>
    <ResponseField name="url" type="string">Media URL for `media` blocks.</ResponseField>
    <ResponseField name="previewUrl" type="string">Preview image URL for `media` blocks.</ResponseField>
    <ResponseField name="width" type="number">Image width in pixels.</ResponseField>
    <ResponseField name="height" type="number">Image height in pixels.</ResponseField>
    <ResponseField name="tweetId" type="string">ID of the post a `tweet` block embeds.</ResponseField>
    <ResponseField name="links" type="object[]">Links in the block's text. Each has the `offset` and `length` of the text it covers, and its `url`.</ResponseField>

    <ResponseField name="inlineStyleRanges" type="object[]">
      Inline text formatting.
      **Style range fields.**
      <ResponseField name="offset" type="number">Character offset where the style starts.</ResponseField>
      <ResponseField name="length" type="number">Number of characters the style spans.</ResponseField>
      <ResponseField name="style" type="string">X's name for the style, such as `Bold` or `Italic`.</ResponseField>
    </ResponseField>
  </ResponseField>

  <ResponseField name="createdAt" type="string">Article creation timestamp.</ResponseField>
  <ResponseField name="likeCount" type="number">Like count.</ResponseField>
  <ResponseField name="replyCount" type="number">Reply count.</ResponseField>
  <ResponseField name="retweetCount" type="number">Reposts of the Article's post. 0 or more.</ResponseField>
  <ResponseField name="quoteCount" type="number">Quote tweet count.</ResponseField>
  <ResponseField name="bookmarkCount" type="number">Accounts that bookmarked the Article's post. 0 or more.</ResponseField>
  <ResponseField name="viewCount" type="number">View count.</ResponseField>
</ResponseField>

<ResponseField name="author" type="object">
  The article author. Omitted if author data is unavailable.
  **Author object fields.**

  <ResponseField name="id" type="string">
    Author user ID.
  </ResponseField>

  <ResponseField name="username" type="string">
    Author X username.
  </ResponseField>

  <ResponseField name="name" type="string">
    Author display name.
  </ResponseField>

  <ResponseField name="profilePicture" type="string">
    Profile picture URL. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="description" type="string">
    Author bio. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="location" type="string">
    Profile location. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="url" type="string">
    Profile website URL. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="createdAt" type="string">
    Account creation timestamp. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="followersCount" type="number">
    Follower count. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="followingCount" type="number">
    Following count. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="statusesCount" type="number">
    Posted tweet count. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="mediaCount" type="number">
    Media post count. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="favouritesCount" type="number">
    Liked tweet count. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="profileBannerUrl" type="string">
    Profile banner URL. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="isBlueVerified" type="boolean">
    Whether the account has X Premium verification. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="isVerified" type="boolean">
    Normalized verification status. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="isTranslator" type="boolean">
    Whether the account is an X translator. Omitted if unavailable.
  </ResponseField>

  <ResponseField name="protected" type="boolean">
    Whether the account protects its posts. Omitted if unavailable.
  </ResponseField>
</ResponseField>

```json theme={null}
{
  "article": {
    "title": "Why Your TikTok & Instagram Videos Aren't Getting Views",
    "previewText": "Creating organic videos for Instagram Reels and TikTok is one of the most effective ways to attract customers...",
    "coverImageUrl": "https://pbs.twimg.com/media/HDcia3lXsAASgbQ.jpg",
    "contents": [
      {
        "type": "paragraph",
        "text": "Creating organic videos for Instagram Reels and TikTok is one of the most effective ways to attract customers."
      },
      {
        "type": "header-two",
        "text": "The problem"
      },
      {
        "type": "ordered-list-item",
        "text": "Your account isn't properly set up"
      },
      {
        "type": "media",
        "url": "https://pbs.twimg.com/media/HDm1eekbQAAzP9c.png",
        "previewUrl": "https://pbs.twimg.com/media/HDm1eekbQAAzP9c.png",
        "width": 640,
        "height": 804
      },
      {
        "type": "paragraph",
        "text": "What really matters is the hook.",
        "inlineStyleRanges": [{ "offset": 23, "length": 8, "style": "Bold" }]
      }
    ],
    "bodyMarkdown": "Creating organic videos for Instagram Reels and TikTok is one of the most effective ways to attract customers.\n\n## The problem\n\n1. Your account isn't properly set up\n\n![](https://pbs.twimg.com/media/HDm1eekbQAAzP9c.png)\n\nWhat really matters is **the hook**.",
    "createdAt": "Tue Mar 17 13:03:00 +0000 2026",
    "likeCount": 156,
    "replyCount": 9,
    "quoteCount": 4,
    "viewCount": 71303
  },
  "author": {
    "id": "1857516996755165184",
    "username": "cesaralvarezll",
    "name": "César Álvarez",
    "profilePicture": "https://pbs.twimg.com/profile_images/1884934857567895553/hHWR_iBg_normal.jpg"
  }
}
```

### 400 Invalid tweet ID

```json theme={null}
{ "error": "invalid_tweet_id" }
```

The provided tweet ID is empty or not a valid format.

### 401 Unauthenticated

```json theme={null}
{ "error": "unauthenticated" }
```

Missing or invalid API key. Check the `x-api-key` header value.

### 402 Payment required

Account keys get account options. Guest keys get guest top-up only.
Anonymous calls receive a direct MPP `WWW-Authenticate: Payment` challenge plus a guest wallet creation action.
No checkout starts automatically. Confirm any payment action.

### 404 Article not found

```json theme={null}
{ "error": "article_not_found", "message": "Article not found. Use an X Article tweet ID." }
```

The tweet is valid but does not contain an X Article.

### 502 X API unavailable

```json theme={null}
{ "error": "x_api_unavailable" }
```

The read service returned an error. Retry after a short delay.

### 429 Rate limit exceeded

```json theme={null}
{ "error": "rate_limit_exceeded", "retryAfter": 60 }
```

You exceeded your tier rate limit. Wait for the `Retry-After` header before retrying.

### 424 Dependency failed

```json theme={null}
{ "error": "x_api_unavailable" }
```

The normalized v1 response contract can return 424 when the read service is unavailable.

<Note>
  **Related.** [Get tweet](/api-reference/x/get-tweet) · [Get tweet thread](/api-reference/x/tweet-thread) · [Search tweets](/api-reference/x/search-tweets) · [Create extraction](/api-reference/extractions/create)
</Note>

<div className="related-api-links">
  <Accordion title="Related tweet, reply & media APIs" icon="link">
    * Tweets: [Get tweet](/api-reference/x/get-tweet) · [Batch tweets](/api-reference/x/batch-tweets) · [Tweet thread](/api-reference/x/tweet-thread) · [Hidden replies](/api-reference/x/tweet-hidden-replies) · [Translate tweet](/api-reference/x/tweet-translation) · [Embed tweet](/api-reference/x/tweet-embed) · [Resolve links](/api-reference/x/resolve-links) · [Tweet subtitles](/api-reference/x/tweet-subtitles) · [X Article](/api-reference/x/get-article)
    * Analysis: [Sentiment analysis](/api-reference/x/sentiment-analysis) · [Brand mentions](/api-reference/x/brand-monitoring) · [News classification](/api-reference/x/news-classification) · [Market signals](/api-reference/x/market-signals) · [Viral score](/api-reference/x/viral-score) · [Classify posts](/api-reference/x/classify-tweets)
    * Engagement: [Tweet replies](/api-reference/x/tweet-replies) · [Quote tweets](/api-reference/x/tweet-quotes) · [Likers](/api-reference/x/favoriters) · [Reposters](/api-reference/x/retweeters) · [Check repost](/api-reference/x/tweet-repost-check)
    * Profiles: [User tweets](/api-reference/x/user-tweets) · [Batch user tweets](/api-reference/x/batch-user-tweets) · [User replies](/api-reference/x/user-replies) · [User likes](/api-reference/x/user-likes) · [User media](/api-reference/x/user-media) · [User highlights](/api-reference/x/user-highlights) · [User articles](/api-reference/x/user-articles)
    * Feeds: [List tweets](/api-reference/x/list-tweets) · [Trends](/api-reference/x/trends) · [Trend locations](/api-reference/x/trend-locations) · [Search Spaces](/api-reference/x/search-spaces) · [Get Space](/api-reference/x/get-space) · [Space replay](/api-reference/x/space-replay) · [Get broadcast](/api-reference/x/get-broadcast) · [Hashflags](/api-reference/x/hashflags) · [Search places](/api-reference/x/search-places) · [Download media](/api-reference/x/download-media)
  </Accordion>
</div>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.