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

# Twitter advanced search API & tweet scraper

> Search tweets by keyword, ID, or URL. Return text, authors, replies, metrics, media, and cursors for CRM, agents, or exports. Costs 1 credit per tweet.

<Panel>
  <Tabs defaultTabIndex={0} sync={false}>
    <Tab title="200" id="response-x-search-tweets-200">
      ```json theme={null}
      {
        "tweets": [
          {
            "id": "1234567890",
            "text": "Just launched our new feature!",
            "createdAt": "2025-01-15T12:00:00Z",
            "likeCount": 42,
            "retweetCount": 5
          }
        ],
        "has_next_page": true,
        "next_cursor": "DAACCgACGRElMJcAAA"
      }
      ```
    </Tab>

    <Tab title="400" id="response-x-search-tweets-400">
      ```json theme={null}
      {
        "error": "missing_query",
        "message": "Search query required. Use q."
      }
      ```
    </Tab>

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

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

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

    <Tab title="404" id="response-x-search-tweets-404">
      ```json theme={null}
      {
        "error": "not_found",
        "message": "Resource not found."
      }
      ```
    </Tab>

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

    <Tab title="410" id="response-x-search-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-search-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-search-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-search-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-search-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>

<Note>
  Requested counts are upper bounds for paid calls. When credits can't cover the
  full page or ID list, Xquik returns fewer results. While `has_next_page` is
  `true`, send `next_cursor` as `cursor` with the same query, filters,
  `queryType` & `limit`. With 0 affordable results, it returns
  `402 insufficient_credits`.
</Note>

Search Tweets accepts keywords, hashtags,
operators, dates, authors, media, and engagement filters. For exact lookup,
send a Tweet ID or X status URL in `q` with no time params. To search one
user's tweets as a plain timeline, call
[Search user tweets](/api-reference/x/user-tweets)
(`GET /x/users/{id}/tweets`). Date params append `since:` and `until:`
search operators to `q`, so
`q=from:username&sinceTime=2026-05-01&untilTime=2026-05-02` stays on search.
Cursor requests return an empty final page for exact IDs.

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

Conversation searches use automatic recovery even when `limit` is 1.
A search with no results ends with `has_next_page=false`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -G https://xquik.com/api/v1/x/tweets/search \
    --data-urlencode "q=giveaway" \
    --data-urlencode "fromUser=username" \
    --data-urlencode "mediaType=media" \
    --data-urlencode "verifiedOnly=true" \
    --data-urlencode "queryType=Latest" \
    -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/tweets/search \
    --data-urlencode "q=giveaway" \
    --data-urlencode "fromUser=username" \
    --data-urlencode "mediaType=media" \
    --data-urlencode "cursor=abc123" \
    -H "x-api-key: xq_your_api_key_here" | jq
  ```

  ```javascript Node.js theme={null}
  const params = new URLSearchParams({
    q: "giveaway",
    fromUser: "username",
    mediaType: "media",
  });
  const response = await fetch(`https://xquik.com/api/v1/x/tweets/search?${params}`, {
    headers: { "x-api-key": "xq_your_api_key_here" },
  });
  const firstPage = await response.json();
  if (!response.ok) throw new Error(JSON.stringify(firstPage));

  let page = firstPage;
  let pageCursor = "";
  const seenCursors = new Set();
  for (let pageIndex = 0; pageIndex < 3; pageIndex += 1) {
    const searchRows = page.tweets.map((tweet) => ({
      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,
      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),
      query: params.get("q"),
      page_index: pageIndex,
      page_cursor: pageCursor,
      next_cursor: page.next_cursor,
      has_next_page: page.has_next_page,
    }));
    for (const row of searchRows) 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 nextParams = new URLSearchParams(params);
    nextParams.set("cursor", pageCursor);
    const nextResponse = await fetch(`https://xquik.com/api/v1/x/tweets/search?${nextParams}`, {
      headers: { "x-api-key": "xq_your_api_key_here" },
    });
    page = await nextResponse.json();
    if (!nextResponse.ok) throw new Error(JSON.stringify(page));
  }
  ```
</CodeGroup>

## Direct API handoff

Use `GET /x/tweets/search` for live JSON. Store IDs and `next_cursor` to resume.
Use [`tweet_search_extractor`](/guides/tweet-scraper-csv-export) for estimates,
saved pages, or downloadable CSV, JSON, and XLSX files.

`limit` bounds unique matching tweets after filtering. Keep `q`, filters,
`queryType`, and `limit` unchanged when resuming with `cursor=next_cursor`.
Continue while `has_next_page` is true. Deduplicate IDs and reject repeated cursors.
Explicit `mode=coverage` returns retained rows when its request window expires.
Check `diagnostic.deadlineReached` and `diagnostic.complete`. Partial coverage does not mean the source ran out of tweets.

For account date windows, `sinceTime` and `untilTime` append `since:` and
`until:` to `q`. Inline `since_time:` and `until_time:`
intersect. The start is inclusive. The end is exclusive. Days are UTC.
`q=from:username&sinceTime=2026-05-01&untilTime=2026-05-02` behaves like
`from:username since:2026-05-01 until:2026-05-02`. Use `queryType=Latest` for
backfills or keywords for ranked search. Bounds apply to every returned page.
Coverage continues past rejected rows.

`-filter:nativeretweets` drops button reposts but keeps quotes & manual RT
text. `include:nativeretweets` adds button reposts to author searches.
`-filter:retweets` also drops manual RT text. Xquik keeps the operator you send.
Dated author searches keep either filter through pagination & recovery, and
read the account timeline too.

Bare `q=from:username` uses automatic timeline and search coverage. Continue
when the response includes `next_cursor`. Use `mode=standard` only when an old
integration requires the legacy single-page timeline behavior.

A search that needs a protected author returns `403` with
`x_account_protected` and no charge. Choose a public account. Searches across
several authors are unaffected.

## Advanced Twitter search patterns

| Search intent | Request pattern |
| - | - |
| Twitter keyword search | `q=product%20launch` |
| Exact hyphenated phrase | `q=battery&exactPhrase=sodium-sulfur%20batteries` |
| Search tweets from one user | `q=from:username` |
| Twitter search by date | `q=launch&sinceTime=2026-05-01&untilTime=2026-05-02` |
| Search hashtag tweets | `q=%23Example` |
| Search tweets with media | `q=launch&mediaType=media` |
| Search verified authors | `q=launch&verifiedOnly=true` |

<CardGroup cols={2}>
  <Card title="Tweet rows" icon="rows-3">
    Store `tweets[]` as the matching tweet rows for app ingestion, analyst export, or retrieval.
  </Card>

  <Card title="Tweet keys" icon="key-round">
    Store `tweets[].id` as the stable tweet key for deduplication, CRM notes, queues, and follow-up lookups.
  </Card>

  <Card title="Search context" icon="message-square-text">
    Store `tweets[].text` and `tweets[].createdAt` for search hit context and time ordering.
  </Card>

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

  <Card title="Scoring fields" icon="chart-no-axes-combined">
    Store engagement counts for scoring, routing, and prioritization.
  </Card>

  <Card title="Relationship context" icon="git-branch">
    Store `tweets[].media`, `quoted_tweet` & `retweeted_tweet` for media & relationship context.
  </Card>

  <Card title="Next page" icon="arrow-right">
    Store `has_next_page` and `next_cursor` as the cursor handoff. For bounded
    `limit` batches, keep the same query, filters, `queryType`, and `limit`
    when resuming.
  </Card>

  <Card title="File exports" icon="file-spreadsheet">
    Use `tweet_search_extractor` when the output must be saved CSV, JSON, or XLSX.
  </Card>
</CardGroup>

Tweet search costs 1 credit per tweet returned. Retry `429` with the `Retry-After` header. Retry `502` after a short backoff. Change the query after `424 search_unavailable`.

## Matched text ranges

Each row says where its `text` matches the search, in `textHighlights`. Use the
ranges to bold the matched words or to cut a snippet around them.

* `startIndex` is the first code point of a match. `endIndex` is the first one after it.
* Offsets count code points of the row's `text`, as `displayTextRange` does.
* JavaScript counts some characters, such as most emoji, as 2 units. Split the text by code point first.
* The list is empty when X marks nothing, such as for a `from:` search without words.
* X marks matches only in a post's first 280 characters. Later matches in a long post get no range.

```javascript theme={null}
const characters = [...tweet.text];
const matches = tweet.textHighlights.map(({ startIndex, endIndex }) =>
  characters.slice(startIndex, endIndex).join(""),
);
```

## Query parameters

<ParamField query="q" type="string" required>
  Send the caller's query, Tweet ID, or status URL. `query` is an alias.
  Quotes match phrases. Hyphens negate terms. Use `exactPhrase` for literals.
</ParamField>

<ParamField query="queryType" type="string">
  `Latest` ranks by time. `Top` ranks engagement. Any case works. `result_type` and `sort_order`
  are accepted aliases, as are `type`, `search_type`, `product`, `category` & `section`.
  `recency` maps to `Latest`. `relevancy` maps to `Top`. `Photos`, `Videos` & `Media`
  read `Latest` with `mediaType` `images`, `videos` or `media`, unless you set
  `mediaType`. `People` & `Lists` answer 400 & point to `/x/users/search` &
  `/x/lists/search`.
</ParamField>

<ParamField query="mode" type="string">
  Optional compatibility override. Omit it for automatic maximum coverage.
  Use `standard` for legacy single-view pagination. Use `coverage` for a
  one-shot diagnostic response without cursor pagination.
</ParamField>

<ParamField query="cursor" type="string">
  Pass `next_cursor` back unchanged. New Xquik cursors resume automatic
  coverage. Existing unprefixed cursors keep legacy behavior.
</ParamField>

<ParamField query="sinceTime" type="string">
  Inclusive lower bound. Intersects with inline bounds.
</ParamField>

<ParamField query="untilTime" type="string">
  Exclusive upper bound. Intersects with inline bounds.
</ParamField>

<ParamField query="limit" type="integer">
  Maximum Tweets per automatic page, from `1` through `10000`. `count` and
  `max_results` are accepted aliases. So are `pageSize`, `maxItems`, `max_items` &
  `per_page`. Keep the value on cursor requests.
</ParamField>

### Structured filters

Structured filters are part of the public Search Tweets API. Use X search
operators. Keep the same filters on every cursor request.

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

### Search-only operators

These query parameters apply only to `GET /x/tweets/search` because they map to
search operators before the request runs. Use `advancedQuery` only when you
already have trusted raw X search operator syntax to append.

<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 matching tweets.
  **Tweet object fields.**
  The API omits fields absent from a source tweet.

  <ResponseField name="id" type="string">Tweet ID.</ResponseField>
  <ResponseField name="text" type="string">Tweet text.</ResponseField>
  <ResponseField name="type" type="string">Tweet type.</ResponseField>
  <ResponseField name="createdAt" type="string">ISO 8601 creation time.</ResponseField>
  <ResponseField name="isNoteTweet" type="boolean">Whether this is a Note Tweet.</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.</ResponseField>
  <ResponseField name="retweetCount" type="number">Repost count.</ResponseField>
  <ResponseField name="replyCount" type="number">Reply count.</ResponseField>
  <ResponseField name="quoteCount" type="number">Quote count.</ResponseField>
  <ResponseField name="viewCount" type="number">View count.</ResponseField>
  <ResponseField name="bookmarkCount" type="number">Bookmark count.</ResponseField>
  <ResponseField name="url" type="string">Tweet URL.</ResponseField>
  <ResponseField name="lang" type="string">Tweet language code.</ResponseField>
  <ResponseField name="isReply" type="boolean">Whether the tweet is a reply.</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.</ResponseField>
  <ResponseField name="source" type="string">Tweet client.</ResponseField>
  <ResponseField name="displayTextRange" type="number[]">Rendered text offsets.</ResponseField>
  <ResponseField name="textHighlights" type="object[]">Where `text` matches the search. Each item has `startIndex` & `endIndex`, counted in code points of `text`. Empty when X marks nothing in the post. X marks matches only in a post's first 280 characters.</ResponseField>
  <ResponseField name="isLimitedReply" type="boolean">Whether replies are limited.</ResponseField>
  <ResponseField name="isQuoteStatus" type="boolean">Whether this tweet quotes another.</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.</ResponseField>
  <ResponseField name="contentDisclosure" type="object">Disclosure labels.</ResponseField>

  <ResponseField name="author" type="object">
    Tweet author profile.
    **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.</ResponseField>
    <ResponseField name="following" type="number">Following count.</ResponseField>
    <ResponseField name="verified" type="boolean">Whether the author is verified.</ResponseField>
    <ResponseField name="profilePicture" type="string">Profile image URL.</ResponseField>
    <ResponseField name="coverPicture" type="string">Cover image URL.</ResponseField>
    <ResponseField name="description" type="string">Profile bio.</ResponseField>
    <ResponseField name="location" type="string">Profile location.</ResponseField>
    <ResponseField name="createdAt" type="string">Account creation date.</ResponseField>
    <ResponseField name="statusesCount" type="number">Total tweet count.</ResponseField>
  </ResponseField>

  <ResponseField name="media" type="object[]">
    Attached media items. Omitted when the tweet has no attached media.
    **Media item fields.**
    <ResponseField name="mediaUrl" type="string">Direct media URL (pbs.twimg.com).</ResponseField>
    <ResponseField name="videoVariants" type="object[]">Video variants. Omit for images.</ResponseField>
    <ResponseField name="type" type="string">Media type: `photo`, `video`, or `animated_gif`.</ResponseField>
    <ResponseField name="url" type="string">Shortened t.co URL from the tweet text.</ResponseField>
  </ResponseField>

  <ResponseField name="quoted_tweet" type="object">Embedded quoted tweet (same shape as tweet object). Omitted if not a quote tweet.</ResponseField>
  <ResponseField name="retweeted_tweet" type="object">Original retweeted tweet (same shape as tweet object). Omitted if not a retweet.</ResponseField>
</ResponseField>

<ResponseField name="has_next_page" type="boolean">
  Whether more results are available. Pass `next_cursor` to fetch the next page.
</ResponseField>

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

<ResponseField name="searchedQuery" type="string">
  Your `q` as X searched it. Present only when Xquik fixed it, such as by removing an unmatched quote X refuses.
</ResponseField>

<ResponseField name="readBackTo" type="string">
  How far back a filtered `Latest` search has read, in UTC. Every newer post was checked, so an empty page still moves this time back.
</ResponseField>

```json theme={null}
{
  "tweets": [
    {
      "id": "1893456789012345678",
      "text": "Tweet content here",
      "createdAt": "2026-02-24T10:00:00.000Z",
      "likeCount": 150,
      "retweetCount": 42,
      "replyCount": 10,
      "quoteCount": 5,
      "viewCount": 12400,
      "bookmarkCount": 8,
      "url": "https://x.com/example_user/status/1893456789012345678",
      "lang": "en",
      "author": {
        "id": "987654321",
        "username": "example_user",
        "name": "Xquik",
        "followers": 10000,
        "verified": true,
        "profilePicture": "https://pbs.twimg.com/profile_images/xquik/photo.jpg"
      },
      "media": [
        {
          "mediaUrl": "https://pbs.twimg.com/media/example.jpg",
          "type": "photo",
          "url": "https://t.co/abc123"
        }
      ]
    }
  ],
  "has_next_page": true,
  "next_cursor": "DAADDAABCgABF..."
}
```

### 400 Missing query

The `q` query parameter is empty or missing.

### 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. Check the `x-api-key` header value.

### 402 Payment required

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

### 404 Missing user or tweet

`user_not_found` means a required user lookup failed. Check the username.
`tweet_not_found` means an exact tweet lookup failed. Check the tweet ID or URL.
Neither means a search completed with no matching tweets.

### 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 Search unavailable

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

X fails this query on every read, so a retry fails too. Follow its message to
fix the search. The normalized v1 contract also returns 424 `x_api_unavailable`.

<Note>
  **Next steps.** [Tweet Search Export Workflow](/guides/tweet-scraper-csv-export) when you need saved CSV, JSON, or XLSX files, [Get Tweet](/api-reference/x/get-tweet) to fetch full details for a specific tweet, or [Get User](/api-reference/x/twitter-profile-lookup) to look up an author profile.
</Note>


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