> ## 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.

# Get X article

> Retrieve one long-form X Article by tweet ID with title, body blocks, cover image, metrics, and author fields

<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 · Accepts [MPP](/mpp/overview)
</Callout>

<Info>
  Get X Article returns one long-form X Article by wrapper tweet ID. It is also
  useful as an X article API, Twitter article API, tweet article API, long-form
  post API, or article body endpoint. The canonical endpoint remains
  `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_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_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"),
    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_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),
      "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 shape durable article handoff rows instead of 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`, and `body_text` with
the downstream record. Store `block_count`, `block_types`, `formatted_blocks`,
and `media_urls` when your archive, content pipeline, 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/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
    content review.
  </Card>

  <Card title="Not an article" icon="circle-alert">
    Treat `article_not_found` as a terminal lookup result for that tweet ID.
    Ask for an X Article URL or use a tweet/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>

Direct article reads cost 5 credits per successful call. For MPP callers, this
endpoint is billed as a fixed charge at USD 0.00105 per call.

## Path parameters

<ParamField path="tweetId" type="string" required>
  Numeric tweet ID of the X Article, 15-20 digits. If you have a tweet URL,
  use the final status ID. Regular tweet URLs can return `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" required>
  Your API key. Session cookie authentication is also supported.
</ParamField>

## Response

<Tabs>
  <Tab title="200 OK">
    <ResponseField name="article" type="object">
      The article data.

      <Expandable title="article object">
        <ResponseField name="title" type="string">Article title.</ResponseField>
        <ResponseField name="previewText" type="string">Short preview/summary text of the article.</ResponseField>
        <ResponseField name="coverImageUrl" type="string">Cover thumbnail image URL.</ResponseField>

        <ResponseField name="contents" type="object[]">
          Article body as an array of content blocks.

          <Expandable title="content block">
            <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`, `media`, 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="inlineStyleRanges" type="object[]">
              Inline text formatting.

              <Expandable title="style range">
                <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">Draft.js style token such as `BOLD` or `ITALIC`.</ResponseField>
              </Expandable>
            </ResponseField>
          </Expandable>
        </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="quoteCount" type="number">Quote tweet count.</ResponseField>
        <ResponseField name="viewCount" type="number">View count.</ResponseField>
      </Expandable>
    </ResponseField>

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

      <Expandable title="author object">
        <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>
      </Expandable>
    </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" }
            ]
          }
        ],
        "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"
      }
    }
    ```
  </Tab>

  <Tab title="400 Invalid tweet ID">
    ```json theme={null}
    { "error": "invalid_tweet_id" }
    ```

    The provided tweet ID is empty or not a valid format.
  </Tab>

  <Tab title="401 Unauthenticated">
    ```json theme={null}
    { "error": "unauthenticated" }
    ```

    Missing or invalid API key. Check the `x-api-key` header value.
  </Tab>

  <Tab title="402 Subscription required">
    ```json theme={null}
    { "error": "no_subscription" }
    ```

    No active subscription or insufficient credits. Possible error values: `no_subscription`, `subscription_inactive`, `no_credits`, `insufficient_credits`.
    For [MPP](/mpp/overview) requests without a valid payment credential, 402 returns a `WWW-Authenticate: Payment` challenge header instead.
  </Tab>

  <Tab title="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.
  </Tab>

  <Tab title="502 X API unavailable">
    ```json theme={null}
    { "error": "x_api_unavailable" }
    ```

    The read service returned an error. Retry after a short delay.
  </Tab>

  <Tab title="429 Rate Limit Exceeded">
    ```json theme={null}
    { "error": "rate_limit_exceeded", "retryAfter": 60 }
    ```

    Your tier rate limit was exceeded. Wait for the `Retry-After` header before retrying.
  </Tab>

  <Tab title="424 Dependency Failed">
    ```json theme={null}
    { "error": "x_api_unavailable" }
    ```

    The normalized v1 response contract can return 424 when the read service is unavailable.
  </Tab>
</Tabs>

<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>
