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

# Search user tweets, profile timeline & cursors

> Read one Twitter or X profile's tweets with full text, replies, reposts, likes, quotes, views, attached media, and cursor pagination. 1 credit per tweet.

<Panel>
  <Tabs defaultTabIndex={0} sync={false}>
    <Tab title="200" id="response-x-user-tweets-200">
      ```json theme={null}
      {
        "tweets": [],
        "has_next_page": false,
        "next_cursor": ""
      }
      ```
    </Tab>

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

    <Tab title="401" id="response-x-user-tweets-401">
      ```json theme={null}
      {
        "error": "unauthenticated",
        "message": "Authentication required."
      }
      ```
    </Tab>

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

    <Tab title="403" id="response-x-user-tweets-403">
      ```json theme={null}
      {
        "error": "x_account_protected",
        "message": "Account is protected. Choose a public account."
      }
      ```
    </Tab>

    <Tab title="404" id="response-x-user-tweets-404">
      ```json theme={null}
      {
        "error": "user_not_found",
        "message": "X user not found. Check the username."
      }
      ```
    </Tab>

    <Tab title="409" id="response-x-user-tweets-409">
      ```json theme={null}
      {
        "error": "coverage_cursor_unavailable",
        "message": "Cursor busy. Retry after the indicated delay."
      }
      ```
    </Tab>

    <Tab title="410" id="response-x-user-tweets-410">
      ```json theme={null}
      {
        "error": "coverage_cursor_gone",
        "message": "Cursor finished, expired, or superseded. Restart pagination without cursor."
      }
      ```
    </Tab>

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

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

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

    <Tab title="503" id="response-x-user-tweets-503">
      ```json theme={null}
      {
        "error": "x_api_unavailable",
        "message": "Maximum coverage is busy. Retry shortly."
      }
      ```
    </Tab>
  </Tabs>
</Panel>

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

<Note>
  Repost records include `retweetedAt`, the repost event's UTC ISO 8601 timestamp. It is `null` when
  that timestamp is unavailable. The API omits it for original posts. The nested original post keeps
  its own creation date. This field does not report every account that reposted a post. [Request
  per-account timestamps with Get retweeters.](/api-reference/x/retweeters#retweet-timestamps)
</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`.

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

<Info>
  Omit `mode` for automatic maximum coverage. Xquik combines available views
  within a short request window. It keeps the existing response shape.
  Pass `next_cursor` back unchanged as `cursor`. Keep the same endpoint, target,
  query, and filters.
</Info>

Existing unprefixed cursors keep their legacy behavior. `after`, `limit`, and
`pageSize` aliases also keep working. Billing still counts only returned rows.
Use `mode=standard` only to force legacy single-view pagination.

A page can be empty or underfilled. Continue while `has_next_page` is `true`.
Stop only after the response reports `has_next_page=false`.

First-page requests do not support `Idempotency-Key` retries.
Repeating a cursorless request starts a separate extraction.
Results returned by that extraction incur their normal charges.
Save each response before requesting its next page.
You cannot replay earlier or terminal responses after their cursors become unavailable.

If automatic coverage is busy, an initial request returns a standard data page.
Live coverage cursors remain atomic. Concurrent use returns
`409 coverage_cursor_unavailable` with exact `Retry-After` seconds. Wait, then
retry the same cursor once.
Repeated busy responses never authorize restarting with another cursor.

Finished, expired, superseded, or identity-mismatched cursors return
`410 coverage_cursor_gone`. The response omits `Retry-After`. Restart without
a cursor. Keep received results. Deduplicate restarted results by `id`.
Malformed cursors return `400 invalid_coverage_cursor`. Restart without them.

This route returns the public profile timeline for one Twitter or X
account. The route is
`GET /api/v1/x/users/{id}/tweets`.

Protected accounts return HTTP 403 with `x_account_protected`. Choose a public account.
This applies to every mode, including continuation requests. Xquik collects no results and charges nothing.
Public profile lookup remains available.

<CodeGroup>
  ```bash cURL theme={null}
  # Username profile timeline
  curl https://xquik.com/api/v1/x/users/elonmusk/tweets \
    -H "x-api-key: xq_your_api_key_here" | jq

  # Numeric user ID profile timeline
  curl https://xquik.com/api/v1/x/users/44196397/tweets \
    -H "x-api-key: xq_your_api_key_here" | jq

  # Replies and parent tweet context
  curl -G https://xquik.com/api/v1/x/users/elonmusk/tweets \
    --data-urlencode "includeReplies=true" \
    --data-urlencode "includeParentTweet=true" \
    -H "x-api-key: xq_your_api_key_here" | jq

  # Page 2 - pass next_cursor from the previous response
  curl -G https://xquik.com/api/v1/x/users/elonmusk/tweets \
    --data-urlencode "cursor=abc123" \
    -H "x-api-key: xq_your_api_key_here" | jq
  ```

  ```javascript Node.js theme={null}
  const userIdOrUsername = "elonmusk";
  const response = await fetch(`https://xquik.com/api/v1/x/users/${userIdOrUsername}/tweets`, {
    headers: { "x-api-key": "xq_your_api_key_here" },
  });
  let page = await response.json();
  if (!response.ok) throw new Error(JSON.stringify(page));

  let pageCursor = "";
  const seenCursors = new Set();
  for (let pageIndex = 0; pageIndex < 3; pageIndex += 1) {
    const timelineRows = page.tweets.map((tweet) => ({
      source_user_id_or_username: userIdOrUsername,
      tweet_id: tweet.id,
      text: tweet.text,
      author_id: tweet.author?.id ?? null,
      author_username: tweet.author?.username ?? null,
      author_name: tweet.author?.name ?? null,
      author_followers: tweet.author?.followers ?? null,
      author_verified: tweet.author?.verified ?? null,
      author_profile_picture: tweet.author?.profilePicture ?? null,
      created_at: tweet.createdAt ?? null,
      is_reply: tweet.isReply ?? false,
      in_reply_to_id: tweet.inReplyToId ?? null,
      like_count: tweet.likeCount ?? null,
      reply_count: tweet.replyCount ?? null,
      retweet_count: tweet.retweetCount ?? null,
      quote_count: tweet.quoteCount ?? null,
      view_count: tweet.viewCount ?? null,
      media_urls: (tweet.media ?? []).map((item) => item.mediaUrl).filter(Boolean),
      page_index: pageIndex,
      page_cursor: pageCursor,
      next_cursor: page.next_cursor,
      has_next_page: page.has_next_page,
    }));
    for (const row of timelineRows) process.stdout.write(`${JSON.stringify(row)}\n`);

    if (!page.has_next_page || page.next_cursor === "") break;
    if (page.next_cursor === pageCursor || seenCursors.has(page.next_cursor)) {
      throw new Error("pagination cursor repeated");
    }

    seenCursors.add(page.next_cursor);
    pageCursor = page.next_cursor;
    const nextResponse = await fetch(
      `https://xquik.com/api/v1/x/users/${userIdOrUsername}/tweets?${new URLSearchParams({ cursor: pageCursor })}`,
      { headers: { "x-api-key": "xq_your_api_key_here" } },
    );
    page = await nextResponse.json();
    if (!nextResponse.ok) throw new Error(JSON.stringify(page));
  }
  ```

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

  user_id_or_username = "44196397"
  page_cursor = ""
  seen_cursors = set()

  for page_index in range(3):
      params = {"cursor": page_cursor} if page_cursor else {}
      response = requests.get(
          f"https://xquik.com/api/v1/x/users/{user_id_or_username}/tweets",
          params=params,
          headers={"x-api-key": "xq_your_api_key_here"},
      )
      page = response.json()
      if not response.ok:
          raise RuntimeError(page)

      for tweet in page["tweets"]:
          timeline_row = {
              "source_user_id_or_username": user_id_or_username,
              "tweet_id": tweet["id"],
              "text": tweet["text"],
              "author_id": (tweet.get("author") or {}).get("id"),
              "author_username": (tweet.get("author") or {}).get("username"),
              "author_name": (tweet.get("author") or {}).get("name"),
              "author_followers": (tweet.get("author") or {}).get("followers"),
              "author_verified": (tweet.get("author") or {}).get("verified"),
              "author_profile_picture": (tweet.get("author") or {}).get("profilePicture"),
              "created_at": tweet.get("createdAt"),
              "is_reply": tweet.get("isReply", False),
              "in_reply_to_id": tweet.get("inReplyToId"),
              "like_count": tweet.get("likeCount"),
              "reply_count": tweet.get("replyCount"),
              "retweet_count": tweet.get("retweetCount"),
              "quote_count": tweet.get("quoteCount"),
              "view_count": tweet.get("viewCount"),
              "media_urls": [
                  item["mediaUrl"] for item in tweet.get("media", []) if item.get("mediaUrl")
              ],
              "page_index": page_index,
              "page_cursor": page_cursor,
              "next_cursor": page["next_cursor"],
              "has_next_page": page["has_next_page"],
          }
          print(json.dumps(timeline_row, separators=(",", ":")))

      if not page["has_next_page"] or not page["next_cursor"]:
          break
      if page["next_cursor"] == page_cursor or page["next_cursor"] in seen_cursors:
          raise RuntimeError("pagination cursor repeated")
      seen_cursors.add(page["next_cursor"])
      page_cursor = page["next_cursor"]
  ```
</CodeGroup>

## User timeline handoff

Use `GET /x/users/{id}/tweets` when a CRM, queue worker, or warehouse job needs
one user's profile timeline. This endpoint accepts a username or numeric
user ID. It returns recent public posts from that profile.
The examples above write JSON Lines rows with the source profile, tweet ID,
text, author ID, username, display name, follower count, verified state, profile
image URL, reply context, engagement counts, media URLs, and cursor fields. A
worker can then resume from the last saved `next_cursor`.

For high-volume timeline pulls, deduplicate tweets by `id`. Continue through empty filtered pages while the cursor advances. Stop with a partial-result status when `next_cursor` is missing or repeats.

## Build a profile timeline job

Choose profile posts, reply context, media filters, or saved cursors.

<CardGroup cols={2}>
  <Card title="Original posts" icon="list">
    Set `replies=exclude` to fetch the profile timeline without replies. Own-thread replies stay.
  </Card>

  <Card title="Replies with context" icon="message-square">
    Set `includeReplies=true` and `includeParentTweet=true` when support,
    community, or research rows need the parent tweet context.
  </Card>

  <Card title="Media timeline" icon="image">
    Use a `mediaType` filter for filtered timeline rows, or switch to
    [`User media`](/api-reference/x/user-media) when every row should contain
    media.
  </Card>

  <Card title="Cursor checkpoint" icon="database">
    Store `page_cursor`, `next_cursor`, and `has_next_page` before requesting
    another page.
  </Card>
</CardGroup>

```json theme={null}
{
  "timeline_job_id": "profile-timeline-q2",
  "timeline_route": "GET /api/v1/x/users/{id}/tweets",
  "user_id_or_username": "elonmusk",
  "include_replies": true,
  "include_parent_tweet": true,
  "cursor_param": "cursor",
  "page_cursor": "",
  "next_cursor": "DAADDAABCgABF...",
  "has_next_page": true,
  "media_handoff_route": "GET /api/v1/x/users/{id}/media"
}
```

## Which timeline endpoint?

* Use `GET /api/v1/x/users/{id}/tweets` for one user's profile timeline. It
  returns original profile posts by default.
* Add `includeReplies=true` when the sync needs replies. Add
  `includeParentTweet=true` when reply rows need parent context.
* Use `GET /api/v1/x/users/{id}/replies` when every page should include replies
  by default.
* Use `GET /api/v1/x/users/{id}/media` when every returned row should contain
  profile media.
* Use `GET /api/v1/x/users/{id}/highlights` for the posts on the Highlights tab.
* Use `GET /api/v1/x/tweets/search` for keyword, operator, or advanced search.
* Use `GET /api/v1/x/timeline` for the authenticated account's home timeline.

## Archive tweets from one profile

Use this user-tweets route when the source profile is already known. It fits
profile timeline exports, account research, and approved historical backfills.

Keep the source username or user ID beside every tweet. Export tweet ID, text,
creation time, author fields, engagement counts, and media URLs. Save the
cursor and collection time for each page.

For a repeatable profile timeline:

* Resolve the profile to a numeric user ID.
* Choose a stable page size.
* Save each page before its next cursor.
* Deduplicate resumed rows by tweet ID.
* Stop when no next page remains.

Use tweet search when the workflow starts with keywords, dates, or operators.
Use user replies when replies need their own feed. Use user media when only
photo, video, or animated GIF tweets matter.

Row order does not identify a tweet. New tweets can change the first
page. Use tweet IDs for updates and deduplication.

The first page opens with the pinned tweet, flagged `isPinned`, as x.com
shows it. Later pages skip it. With replies included, it never opens the page.

Rows stay in newest-first date order on every page. x.com's Posts tab
differs: it groups each self-thread root first and hides reposts. Pass
`retweets=exclude` to drop reposts.

| Profile timeline column | Response source | Archive rule |
| - | - | - |
| `source_x_user_id` | Resolved profile ID | Keep one stable source ID across username changes. |
| `source_username` | Requested or resolved username | Keep the readable profile handle observed during collection. |
| `tweet_id` | `tweets[].id` | Use as the stable tweet upsert and deduplication key. |
| `tweet_url` | `tweets[].url` | Open the original X post during review. |
| `text` | `tweets[].text` | Keep the complete tweet or note-tweet text returned by the API. |
| `created_at` | `tweets[].createdAt` | Order archived tweets by creation time, not cursor position. |
| `is_reply` | `tweets[].isReply` | Separate original profile posts from replies. |
| `in_reply_to_id` | `tweets[].inReplyToId` | Join a reply to its immediate parent when returned. |
| `conversation_id` | `tweets[].conversationId` | Group tweets from one conversation thread. |
| `media_urls` | `tweets[].media[].mediaUrl` | Keep image, video, or animated GIF URLs. |
| `page_cursor` | Request `cursor` | Record which profile timeline page produced the row. |
| `collected_at` | Integration timestamp | Tell repeated profile timeline snapshots apart. |

| Timeline requirement | Route and parameters | Result shape |
| - | - | - |
| Original profile posts | `/x/users/{id}/tweets` | Profile tweets without replies by default. |
| Profile posts and replies | `/x/users/{id}/tweets?includeReplies=true` | Original posts plus reply rows. |
| Replies with parent context | Add `includeParentTweet=true` | Reply rows with parent tweet context when available. |
| Media-only profile feed | `/x/users/{id}/media` | Tweets containing profile media. |
| Highlights tab posts | `/x/users/{id}/highlights` | Highlighted posts, in X's order. |
| Keyword or date search | `/x/tweets/search` | Search results selected by query, operator, date, or media filters. |
| Connected account home feed | `/x/timeline` | Ranked home timeline rows for the authenticated account. |

## Path parameters

<ParamField path="id" type="string" required>
  User ID, username with or without `@`, or URL-encoded profile URL, such as
  `x.com/nasa`. See [path IDs](/api-reference/overview#path-ids).
</ParamField>

## Query parameters

<ParamField query="cursor" type="string">
  Pagination cursor for the profile timeline. Omit it for the first page, then
  pass the `next_cursor` value from the previous response to fetch the next
  page.
</ParamField>

<ParamField query="mode" type="string">
  Omit it for automatic maximum coverage. Use `standard` for legacy pagination.
  Search filters such as `keywords` apply only without it.
</ParamField>

<ParamField query="pageSize" type="number" default="20">
  Automatic pages accept `1` through `300`. Unprefixed legacy cursors accept
  `1` through `100`. Source availability, filters, or credits can return fewer.
</ParamField>

<ParamField query="sinceTime" type="string">
  Keep posts created at or after this time. Send ISO 8601, such as
  `2026-09-25T19:20:41Z`, or Unix seconds. A time without an offset is UTC.
</ParamField>

<ParamField query="untilTime" type="string">
  Keep posts created before this time, in the same formats. With `sinceDate` or
  `untilDate`, the narrower window applies.
</ParamField>

<ParamField query="includeReplies" type="boolean" default="false">
  Include reply tweets. Default: `false`. Explicit `replies` takes precedence.

  When you exclude replies, the author's replies to their own posts stay. They continue a thread, and X shows them on the Posts tab.
</ParamField>

<ParamField query="includeParentTweet" type="boolean" default="false">
  Include parent tweet context for returned replies. Defaults to `false`. Set it
  to `true` when replies need conversation context. Search filters such as
  `keywords` do not apply with it.
</ParamField>

### Tweet result filters

These optional filters apply to `tweets[]` returned by this route. They keep the
same user target. Xquik filters rows after it fetches each page. Selective
filters can return fewer rows than an unfiltered page.

<ParamField query="fromUser" type="string">
  Filter to posts from this username. The `@` prefix is optional.
</ParamField>

<ParamField query="toUser" type="string">
  Filter to replies directed to this username.
</ParamField>

<ParamField query="mentioning" type="string">
  Filter to posts that mention this username.
</ParamField>

<ParamField query="language" type="string">
  Only include posts with this language code.
</ParamField>

<ParamField query="sinceDate" type="string">
  Include posts created on or after this date or timestamp.
</ParamField>

<ParamField query="untilDate" type="string">
  Include posts up to this date or timestamp. A date is a UTC day, inclusive, so its own posts count.
</ParamField>

<ParamField query="mediaType" type="string">
  Use `images`, `videos`, `gifs`, `media`, `links`, or `none`.
</ParamField>

<ParamField query="minLikes" type="integer">
  Require this minimum like count.
</ParamField>

<ParamField query="minRetweets" type="integer">
  Require this minimum repost count.
</ParamField>

<ParamField query="minReplies" type="integer">
  Require this minimum reply count.
</ParamField>

<ParamField query="minQuotes" type="integer">
  Require this minimum quote count.
</ParamField>

<ParamField query="minViews" type="integer">
  Require this minimum view count.
</ParamField>

<ParamField query="minBookmarks" type="integer">
  Require this minimum bookmark count.
</ParamField>

<ParamField query="maxFaves" type="integer">
  Allow this maximum like count. Missing counts pass.
</ParamField>

<ParamField query="maxRetweets" type="integer">
  Allow this maximum repost count. Missing counts pass.
</ParamField>

<ParamField query="maxReplies" type="integer">
  Allow this maximum reply count. Missing counts pass.
</ParamField>

<ParamField query="maxQuotes" type="integer">
  Allow this maximum quote count. Missing counts pass.
</ParamField>

<ParamField query="blueVerifiedOnly" type="boolean">
  When `true`, only return posts from Blue-verified authors.
</ParamField>

<ParamField query="verifiedOnly" type="boolean">
  When `true`, only return posts from verified authors.
</ParamField>

<ParamField query="replies" type="string">
  Use `include`, `exclude`, or `only` for replies.
  This setting overrides `includeReplies` when the endpoint supports both.
</ParamField>

<ParamField query="retweets" type="string">
  Use `include`, `exclude`, or `only` for reposts.
</ParamField>

<ParamField query="exactPhrase" type="string">
  Match this literal phrase, including any hyphens.
</ParamField>

<ParamField query="excludeWords" type="string">
  Exclude comma-separated or whitespace-separated terms.
</ParamField>

<ParamField query="anyWords" type="string">
  Require at least 1 comma-separated or whitespace-separated term.
</ParamField>

<ParamField query="hashtags" type="string">
  Match these hashtags. Separate values with commas or spaces.
</ParamField>

<ParamField query="cashtags" type="string">
  Match these cashtags. Separate values with commas or spaces.
</ParamField>

<ParamField query="quotes" type="string">
  Use `include`, `exclude`, or `only` for quote posts.
</ParamField>

<ParamField query="url" type="string">
  URL substring or domain that must appear in tweet URL entities.
</ParamField>

<ParamField query="conversationId" type="string">
  Filter to tweets in this conversation thread.
</ParamField>

<ParamField query="inReplyToTweetId" type="string">
  Only include replies to this tweet ID.
</ParamField>

<ParamField query="quotesOfTweetId" type="string">
  Filter to quote tweets of this tweet ID.
</ParamField>

<ParamField query="retweetsOfTweetId" type="string">
  Filter to retweets of this tweet ID.
</ParamField>

<ParamField query="cardName" type="string">
  Keep only Tweets with this card type, such as `poll2choice_text_only`. Tweet
  search checks each Tweet's card.
</ParamField>

<ParamField query="source" type="string">
  X search no longer supports `source`. A Tweet search with it answers 424 &
  charges nothing.
</ParamField>

<ParamField query="excludeSource" type="string">
  X search no longer supports `excludeSource`. A Tweet search with it answers 424
  & charges nothing.
</ParamField>

<ParamField query="geocode" type="string">
  X search no longer supports `geocode`. A Tweet search with it answers 424 &
  charges nothing.
</ParamField>

<ParamField query="near" type="string">
  Match this place name.
</ParamField>

<ParamField query="within" type="string">
  X search no longer supports `within`. A Tweet search with it answers 424 &
  charges nothing. Use `near` alone.
</ParamField>

<ParamField query="safe" type="boolean">
  When `true`, enable X safe-search filtering.
</ParamField>

<ParamField query="news" type="boolean">
  X no longer searches `filter:news`, so leave this unset.
</ParamField>

<ParamField query="sinceId" type="string">
  Return Tweets whose IDs exceed this ID.
</ParamField>

<ParamField query="maxId" type="string">
  Return Tweets at or below this ID.
</ParamField>

<ParamField query="nativeRetweets" type="boolean">
  When `true`, only return native reposts.
</ParamField>

<ParamField query="withinTime" type="string">
  Match Tweets from this recent window, such as `90m` or `7d`. Use a whole number & `s`, `m`, `h` or `d`.
</ParamField>

<ParamField query="keywords" type="string">
  Words the Tweets must match, in X search syntax.
</ParamField>

<ParamField query="place" type="string">
  Search within this X place ID. [Search places](/api-reference/x/search-places) finds the ID by name.
</ParamField>

<ParamField query="placeCountry" type="string">
  Search within this country code.
</ParamField>

<ParamField query="pointRadius" type="string">
  Geo point radius in X search syntax, such as `-73.99 40.73 25mi`.
</ParamField>

<ParamField query="boundingBox" type="string">
  Geo bounding box in X search syntax, such as `-74.1 40.6 -73.9 40.8`.
</ParamField>

<ParamField query="advancedQuery" type="string">
  Raw X search operators appended to the final search query.
</ParamField>

<ParamField query="listId" type="string">
  Search within this X List ID.
</ParamField>

## 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` for `paid_reads`.
</ParamField>

## Response

### 200 OK

<ResponseField name="tweets" type="object[]">
  Array of tweets by the user.
  **Tweet object fields.**

  <ResponseField name="id" type="string">Tweet ID.</ResponseField>
  <ResponseField name="text" type="string">Tweet text content.</ResponseField>
  <ResponseField name="type" type="string">Tweet type. Omitted if unavailable.</ResponseField>
  <ResponseField name="createdAt" type="string">ISO 8601 creation timestamp. Omitted if unavailable.</ResponseField>
  <ResponseField name="isNoteTweet" type="boolean">Whether this is a Note Tweet (long-form post). Omitted if unavailable.</ResponseField>
  <ResponseField name="isPinned" type="boolean">Whether the author pinned this post to their profile. 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 the tweet is a reply. Omitted if unavailable.</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 not a reply.</ResponseField>
  <ResponseField name="inReplyToUsername" type="string">Username being replied to. Omitted if not a reply.</ResponseField>
  <ResponseField name="conversationId" type="string">Conversation thread ID. Omitted if unavailable.</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="isRetweet" type="boolean">Whether this row is a retweet. `text` carries the original post in full.</ResponseField>
  <ResponseField name="entities" type="object">Parsed entities (URLs, hashtags, mentions). 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.
    **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="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">Profile picture URL. Omitted if unavailable.</ResponseField>
  </ResponseField>

  <ResponseField name="media" type="object[]">
    Media attachments. Omitted if unavailable.
    **Media item fields.**
    <ResponseField name="mediaUrl" type="string">Direct media URL.</ResponseField>
    <ResponseField name="videoVariants" type="object[]">Available video renditions with bitrate, content type, and URL. Omitted for images.</ResponseField>
    <ResponseField name="type" type="string">Media type.</ResponseField>
    <ResponseField name="url" type="string">Shortened URL from the tweet text.</ResponseField>
  </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>
</ResponseField>

<ResponseField name="has_next_page" type="boolean">
  Whether more results are available.
</ResponseField>

<ResponseField name="next_cursor" type="string">
  Opaque cursor for the next page. Empty string when no more results.
</ResponseField>

```json theme={null}
{
  "tweets": [
    {
      "id": "1893456789012345678",
      "text": "User's tweet content",
      "createdAt": "2026-02-24T10:00:00.000Z",
      "likeCount": 500,
      "retweetCount": 120,
      "replyCount": 45,
      "viewCount": 25000,
      "contentDisclosure": {
        "advertising": { "isPaidPromotion": true },
        "aiGenerated": {
          "detectionSource": "GrokSignature",
          "hasAiGeneratedMedia": true
        }
      },
      "url": "https://x.com/elonmusk/status/1893456789012345678",
      "author": {
        "id": "44196397",
        "username": "elonmusk",
        "name": "Elon Musk",
        "followers": 150000000,
        "verified": true,
        "profilePicture": "https://pbs.twimg.com/profile_images/example.jpg"
      },
      "media": [{ "type": "photo", "mediaUrl": "https://pbs.twimg.com/media/example.jpg" }]
    }
  ],
  "has_next_page": true,
  "next_cursor": "DAADDAABCgABF..."
}
```

### 400 Invalid user ID

```json theme={null}
{
  "error": "invalid_user_id",
  "message": "Send a user ID, @username or profile URL, such as x.com/nasa."
}
```

The user ID is empty or invalid.

### 404 User not found

```json theme={null}
{ "error": "user_not_found", "message": "X user not found. Check the username." }
```

The username or user ID doesn't resolve.

### 401 Unauthenticated

Anonymous requests get `WWW-Authenticate: Bearer` and a guest wallet checkout action. This is not a Payment challenge.

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

Missing or invalid API key.

### 402 Payment required

Account keys get account options. Guest keys get guest top-up only.
No checkout starts automatically. Confirm any payment action.

### 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.** [User replies timeline](/api-reference/x/user-replies) · [User media](/api-reference/x/user-media) · [User highlights](/api-reference/x/user-highlights) · [User likes](/api-reference/x/user-likes) · [User mentions timeline](/api-reference/x/user-mentions)
</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>

<div className="related-api-links">
  <Accordion title="Related timeline, bookmark & notification APIs" icon="link">
    * Account feeds: [Home timeline](/api-reference/x/timeline) · [Notifications](/api-reference/x/notifications) · [Mentions](/api-reference/x/user-mentions)
    * Saved and private: [Bookmarks](/api-reference/x/bookmarks) · [Bookmark folders](/api-reference/x/bookmark-folders) · [DM history](/api-reference/x/dm-history)
  </Accordion>
</div>


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