> ## 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 tweet thread

> Retrieve tweet thread context around one tweet with author fields, reply joins, media, and cursor checkpoints

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

<Note>
  Requested result counts are upper bounds for paid authenticated calls. When remaining credits cannot cover the full page or ID list, Xquik returns fewer results. If zero paid results are affordable, it returns `402 insufficient_credits`.
</Note>

<Callout icon="coins" color="#5c3327">
  **1 credit per tweet returned** · [All plans](https://xquik.com/#pricing) from \$0.00012/credit · Accepts [MPP](/mpp/overview)
</Callout>

<Info>
  Get tweet thread returns tweet rows in the conversation thread around one
  source tweet. It is also useful as a tweet thread API, X thread API, Twitter
  thread API, conversation thread API, or thread context endpoint. The canonical
  endpoint remains `GET /api/v1/x/tweets/{id}/thread`.
</Info>

<CodeGroup>
  ```bash First page theme={null}
  curl "https://xquik.com/api/v1/x/tweets/1893456789012345678/thread" \
    -H "x-api-key: xq_YOUR_KEY_HERE" | jq
  ```

  ```bash Next page theme={null}
  curl -G https://xquik.com/api/v1/x/tweets/1893456789012345678/thread \
    --data-urlencode "cursor=abc123" \
    -H "x-api-key: xq_YOUR_KEY_HERE" | jq
  ```

  ```javascript Node.js theme={null}
  const tweetId = "1893456789012345678";
  const cursor = process.env.XQUIK_CURSOR ?? "";
  const params = new URLSearchParams();
  if (cursor !== "") {
    params.set("cursor", cursor);
  }
  const query = params.toString();
  const url = `https://xquik.com/api/v1/x/tweets/${tweetId}/thread${query ? `?${query}` : ""}`;
  const response = await fetch(url, {
    headers: { "x-api-key": "xq_YOUR_KEY_HERE" },
  });
  const data = await response.json();
  const nextCursor = data.has_next_page ? data.next_cursor : null;
  const threadRows = data.tweets.map((tweet) => {
    const author = tweet.author ?? {};

    return {
      source_tweet_id: tweetId,
      thread_tweet_id: tweet.id,
      text: tweet.text,
      author_id: author.id ?? null,
      author_username: author.username ?? null,
      author_name: author.name ?? null,
      author_followers: author.followers ?? null,
      author_verified: author.verified ?? null,
      author_profile_picture: author.profilePicture ?? null,
      created_at: tweet.createdAt ?? null,
      conversation_id: tweet.conversationId ?? null,
      in_reply_to_id: tweet.inReplyToId ?? null,
      media_urls: tweet.media?.map((item) => item.mediaUrl).filter(Boolean) ?? [],
    };
  });
  const checkpoint = { source_tweet_id: tweetId, next_cursor: nextCursor };

  for (const row of threadRows) {
    process.stdout.write(`${JSON.stringify(row)}\n`);
  }
  process.stdout.write(`${JSON.stringify({ checkpoint })}\n`);
  ```

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

  tweet_id = "1893456789012345678"
  cursor = ""
  params = {"cursor": cursor} if cursor else None
  response = requests.get(
      f"https://xquik.com/api/v1/x/tweets/{tweet_id}/thread",
      params=params,
      headers={"x-api-key": "xq_YOUR_KEY_HERE"},
  )
  data = response.json()
  next_cursor = data["next_cursor"] if data["has_next_page"] else None
  thread_rows = []
  for tweet in data["tweets"]:
      author = tweet.get("author") or {}
      thread_rows.append(
          {
              "source_tweet_id": tweet_id,
              "thread_tweet_id": tweet["id"],
              "text": tweet["text"],
              "author_id": author.get("id"),
              "author_username": author.get("username"),
              "author_name": author.get("name"),
              "author_followers": author.get("followers"),
              "author_verified": author.get("verified"),
              "author_profile_picture": author.get("profilePicture"),
              "created_at": tweet.get("createdAt"),
              "conversation_id": tweet.get("conversationId"),
              "in_reply_to_id": tweet.get("inReplyToId"),
              "media_urls": [
                  item["mediaUrl"]
                  for item in tweet.get("media", [])
                  if item.get("mediaUrl")
              ],
          }
      )
  checkpoint = {"source_tweet_id": tweet_id, "next_cursor": next_cursor}

  for row in thread_rows:
      print(json.dumps(row))
  print(json.dumps({"checkpoint": checkpoint}))
  ```
</CodeGroup>

The Node.js and Python snippets write JSON Lines thread rows plus a separate
checkpoint instead of raw response pages. Persist each mapped row and the latest
`next_cursor` so a support timeline, research job, moderation queue, or agent
handoff can resume from the last completed page without duplicate rows.

## Direct tweet thread handoff

Use `GET /api/v1/x/tweets/{id}/thread` when a workflow needs ordered thread
context as durable rows around one tweet. Store `source_tweet_id`,
`thread_tweet_id`, `text`, `author_id`, `author_username`, `author_name`,
`author_followers`, `author_verified`, `author_profile_picture`, `created_at`,
`conversation_id`, `in_reply_to_id`, media URLs, and a separate `next_cursor`
checkpoint for downstream jobs.

<CardGroup cols={2}>
  <Card title="Thread rows" icon="list-tree">
    Store `tweets[]` as the thread context rows returned for one source tweet.
  </Card>

  <Card title="Stable upserts" icon="key-round">
    Store `tweets[].id` as `thread_tweet_id` with `source_tweet_id` for
    idempotent imports.
  </Card>

  <Card title="Reply joins" icon="message-square-reply">
    Store `conversationId`, `inReplyToId`, `inReplyToUserId`, and
    `inReplyToUsername` to rebuild thread structure.
  </Card>

  <Card title="Author joins" icon="user-round">
    Store `tweets[].author.id`, `username`, `name`, `followers`, `verified`, and
    `profilePicture` for review, CRM, or research tools.
  </Card>

  <Card title="Media context" icon="image">
    Store `media[].mediaUrl`, `entities`, `quoted_tweet`, and `retweeted_tweet`
    when returned to preserve attached context.
  </Card>

  <Card title="Next page" icon="arrow-right">
    Store `has_next_page` and `next_cursor`; pass `next_cursor` back as `cursor`
    only when `has_next_page` is true.
  </Card>

  <Card title="Credit-limited pages" icon="coins">
    Use `tweets.length`, not a requested page size, for row counts. Low balances
    can return fewer rows.
  </Card>

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

Direct tweet thread reads cost 1 credit per tweet returned. Low credit balances
can return fewer tweets than a full page; zero affordable results return
`402 insufficient_credits`. For MPP callers, this endpoint is billed as a
session at USD 0.00015 per tweet returned.

## Path parameters

<ParamField path="id" type="string" required>
  Tweet ID (numeric string).
</ParamField>

## Query parameters

<ParamField query="cursor" type="string">
  Pagination cursor from `next_cursor` in a previous response. Omit for the
  first page. Pass a cursor only when `has_next_page` is true.
</ParamField>

## Which thread endpoint?

<CardGroup cols={2}>
  <Card title="Tweet thread" icon="list-tree">
    Use `GET /x/tweets/{id}/thread` for conversation thread context around one
    tweet.
  </Card>

  <Card title="Tweet replies" icon="message-square-reply">
    Use [`GET /x/tweets/{id}/replies`](/api-reference/x/tweet-replies) when you
    need reply tweet rows under one source tweet.
  </Card>

  <Card title="Quote tweets" icon="quote">
    Use [`GET /x/tweets/{id}/quotes`](/api-reference/x/tweet-quotes) for tweet
    rows that quote one source tweet.
  </Card>

  <Card title="Search tweets" icon="search">
    Use [`GET /x/tweets/search`](/api-reference/x/search-tweets) when you need
    keyword, operator, or structured-filter discovery across many tweets.
  </Card>

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

  <Card title="Single tweet" icon="message-square">
    Use [`Get tweet`](/api-reference/x/get-tweet) when you only need one tweet
    object by ID.
  </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="tweets" type="object[]">
      Array of tweets in the thread, ordered chronologically.

      <Expandable title="Tweet object fields">
        <ResponseField name="id" type="string">Tweet ID.</ResponseField>
        <ResponseField name="text" type="string">Tweet text.</ResponseField>
        <ResponseField name="type" type="string">Tweet type. Omitted if unavailable.</ResponseField>
        <ResponseField name="createdAt" type="string">ISO 8601 creation timestamp.</ResponseField>
        <ResponseField name="isNoteTweet" type="boolean">Whether this is a Note Tweet. Omitted if unavailable.</ResponseField>
        <ResponseField name="likeCount" type="number">Like count. Omitted if unavailable.</ResponseField>
        <ResponseField name="retweetCount" type="number">Retweet count. Omitted if unavailable.</ResponseField>
        <ResponseField name="replyCount" type="number">Reply count. Omitted if unavailable.</ResponseField>
        <ResponseField name="quoteCount" type="number">Quote tweet count. Omitted if unavailable.</ResponseField>
        <ResponseField name="viewCount" type="number">View count. Omitted if unavailable.</ResponseField>
        <ResponseField name="bookmarkCount" type="number">Bookmark count. Omitted if unavailable.</ResponseField>
        <ResponseField name="url" type="string">Permalink URL on X. Omitted if unavailable.</ResponseField>
        <ResponseField name="lang" type="string">Tweet language code. Omitted if unavailable.</ResponseField>
        <ResponseField name="isReply" type="boolean">Whether this tweet is a reply in the thread.</ResponseField>
        <ResponseField name="inReplyToId" type="string">Tweet ID being replied to. Omitted if not a reply.</ResponseField>
        <ResponseField name="inReplyToUserId" type="string">User ID being replied to. Omitted if unavailable.</ResponseField>
        <ResponseField name="inReplyToUsername" type="string">Username being replied to. Omitted if unavailable.</ResponseField>
        <ResponseField name="conversationId" type="string">Thread conversation ID.</ResponseField>
        <ResponseField name="source" type="string">Client used to post the tweet. Omitted if unavailable.</ResponseField>
        <ResponseField name="displayTextRange" type="number[]">Start and end offsets for rendered tweet text. Omitted if unavailable.</ResponseField>
        <ResponseField name="isLimitedReply" type="boolean">Whether replies are limited. Omitted if unavailable.</ResponseField>
        <ResponseField name="isQuoteStatus" type="boolean">Whether this tweet quotes another tweet. Omitted if unavailable.</ResponseField>
        <ResponseField name="entities" type="object">Parsed entities. Omitted if unavailable.</ResponseField>
        <ResponseField name="contentDisclosure" type="object">Disclosure metadata for paid partnership and AI-generated media labels. Includes `advertising.isPaidPromotion` and `aiGenerated.hasAiGeneratedMedia` when X returns them. Omitted if unavailable.</ResponseField>

        <ResponseField name="author" type="object">
          Tweet author profile. Omitted if unavailable.

          <Expandable title="Author object fields">
            <ResponseField name="id" type="string">Author user ID.</ResponseField>
            <ResponseField name="username" type="string">Author handle without `@`.</ResponseField>
            <ResponseField name="name" type="string">Author display name. Omitted if unavailable.</ResponseField>
            <ResponseField name="followers" type="number">Follower count. Omitted if unavailable.</ResponseField>
            <ResponseField name="verified" type="boolean">Whether the author is verified. Omitted if unavailable.</ResponseField>
            <ResponseField name="profilePicture" type="string">Author profile image URL. Omitted if unavailable.</ResponseField>
          </Expandable>
        </ResponseField>

        <ResponseField name="media" type="object[]">
          Media attachments. Omitted when the tweet has no media.

          <Expandable title="Media object fields">
            <ResponseField name="mediaUrl" type="string">Direct media URL.</ResponseField>
            <ResponseField name="type" type="string">Media type.</ResponseField>
            <ResponseField name="url" type="string">Shortened URL from the tweet text.</ResponseField>
          </Expandable>
        </ResponseField>

        <ResponseField name="quoted_tweet" type="object">Embedded quoted tweet. Omitted if not a quote tweet.</ResponseField>
        <ResponseField name="retweeted_tweet" type="object">Original retweeted tweet. Omitted if not a retweet.</ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="has_next_page" type="boolean">Whether more results are available.</ResponseField>
    <ResponseField name="next_cursor" type="string">Cursor for the next page.</ResponseField>

    ```json theme={null}
    {
      "tweets": [
        {
          "id": "1893456789012345678",
          "text": "Thread starts here...",
          "createdAt": "2026-03-27T10:00:00.000Z",
          "author": {
            "id": "9876543210",
            "username": "xquik",
            "name": "Xquik",
            "followers": 12000,
            "verified": true,
            "profilePicture": "https://pbs.twimg.com/profile_images/example.jpg"
          }
        }
      ],
      "has_next_page": false,
      "next_cursor": ""
    }
    ```
  </Tab>

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

  <Tab title="401 Unauthenticated">
    ```json theme={null}
    { "error": "unauthenticated" }
    ```
  </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`.
  </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:** [Tweet replies](/api-reference/x/tweet-replies) · [Quote tweets](/api-reference/x/tweet-quotes) · [Retweeters](/api-reference/x/retweeters) · [Create extraction](/api-reference/extractions/create)
</Note>
