> ## 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 batch tweet lookup API & post details

> Retrieve up to 100 tweets by ID in one request with full text, authors, media, reply and quote context, engagement metrics, and URLs. 1 credit per tweet.

<Panel>
  <Tabs defaultTabIndex={0} sync={false}>
    <Tab title="200" id="response-x-batch-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": false,
        "next_cursor": ""
      }
      ```
    </Tab>

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

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

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

    <Tab title="424" id="response-x-batch-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-batch-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-batch-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-batch-tweets-503">
      ```json theme={null}
      {
        "error": "x_api_unavailable",
        "message": "Xquik is busy right now. Retry shortly."
      }
      ```
    </Tab>
  </Tabs>
</Panel>

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

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

## When to use batch lookup

Use this route to look up as many as 100 tweet IDs at once. It charges only for returned tweets. Use list-tweets when reading a list feed instead.

<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** · Accepts account credits and guest `paid_reads`
</Callout>

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://xquik.com/api/v1/x/tweets?ids=1893456789012345678,1893456789012345679" \
    -H "x-api-key: xq_your_api_key_here" | jq
  ```

  ```javascript Node.js theme={null}
  const ids = ["1893456789012345678", "1893456789012345679"];
  const response = await fetch(`https://xquik.com/api/v1/x/tweets?ids=${ids.join(",")}`, {
    headers: { "x-api-key": "xq_your_api_key_here" },
  });
  const data = await response.json();
  const tweetsById = new Map(data.tweets.map((tweet) => [tweet.id, tweet]));
  const tweetRows = data.tweets.map((tweet) => {
    const author = tweet.author ?? {};

    return {
      requested_ids: ids,
      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,
      like_count: tweet.likeCount ?? null,
      reply_count: tweet.replyCount ?? null,
      retweet_count: tweet.retweetCount ?? null,
      quote_count: tweet.quoteCount ?? null,
      media_urls: tweet.media?.map((item) => item.mediaUrl).filter(Boolean) ?? [],
      has_next_page: data.has_next_page,
      next_cursor: data.next_cursor || null,
    };
  });
  const missingIds = ids.filter((id) => !tweetsById.has(id));
  ```

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

  ids = ["1893456789012345678", "1893456789012345679"]
  response = requests.get(
      "https://xquik.com/api/v1/x/tweets",
      params={"ids": ",".join(ids)},
      headers={"x-api-key": "xq_your_api_key_here"},
  )
  data = response.json()
  tweets_by_id = {tweet["id"]: tweet for tweet in data["tweets"]}
  tweet_rows = []
  for tweet in data["tweets"]:
      author = tweet.get("author") or {}
      tweet_rows.append(
          {
              "requested_ids": ids,
              "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"),
              "like_count": tweet.get("likeCount"),
              "reply_count": tweet.get("replyCount"),
              "retweet_count": tweet.get("retweetCount"),
              "quote_count": tweet.get("quoteCount"),
              "media_urls": [
                  item["mediaUrl"]
                  for item in tweet.get("media", [])
                  if item.get("mediaUrl")
              ],
              "has_next_page": data["has_next_page"],
              "next_cursor": data["next_cursor"] or None,
          }
      )

  missing_ids = [tweet_id for tweet_id in ids if tweet_id not in tweets_by_id]
  ```
</CodeGroup>

The Node.js and Python snippets build tweet rows. They do not print
full response pages. Store `tweetRows` or `tweet_rows` and `missingIds` or
`missing_ids` with the original ID list. Retries then request only missing tweets.

## Direct batch tweet handoff

Use `GET /x/tweets` when a CRM, warehouse, newsroom, moderation queue, or agent
workflow already has tweet IDs. One response returns tweet text, authors, metrics,
media URLs, and missing-ID handling. Use [Get Tweet](/api-reference/x/get-tweet)
for one tweet by ID. Use [Search Tweets](/api-reference/x/search-tweets)
to find tweets by query.

Store `requested_ids`, `tweet_id`, `text`, `author_id`, `author_username`,
`author_name`, `author_followers`, `author_verified`,
`author_profile_picture`, `created_at`, `conversation_id`, engagement counts,
media URLs, `has_next_page`, and `next_cursor`. Join returned tweets by
`tweet_id`. Do not rely on response order. Send at most 100 IDs per request. Batch requests
always return `has_next_page: false` and `next_cursor: ""`. Zero affordable
results return `402 insufficient_credits`.

## Store exact batch lookup results

Keep the original request order separately. Join returned tweets by numeric
Tweet ID because unavailable or unaffordable IDs can be absent.

| Batch lookup column | Response source | Reconciliation rule |
| - | - | - |
| `batch_id` | Integration value | Group every requested and returned Tweet ID. |
| `requested_tweet_id` | Parsed `ids` input | Keep all requested IDs, including missing ones. |
| `tweet_id` | `tweets[].id` | Join a returned tweet to its request row. |
| `text` | `tweets[].text` | Keep the returned tweet or Note Tweet text. |
| `author_id` | `tweets[].author.id` | Keep a stable author key. |
| `author_username` | `tweets[].author.username` | Display the returned author handle. |
| `created_at` | `tweets[].createdAt` | Keep the tweet publication time. |
| `conversation_id` | `tweets[].conversationId` | Join returned tweets to conversation workflows. |
| `media_urls` | `tweets[].media[].mediaUrl` | Keep image and video URLs for returned tweets. |
| `lookup_status` | Derived from returned IDs | Set `returned` or `missing` for every requested ID. |
| `has_next_page` | Response value | Expect `false` for an exact batch request. |
| `next_cursor` | Response value | Expect an empty string. Never paginate an exact batch. |

| Input or result condition | Handling |
| - | - |
| More than 100 Tweet IDs | Split the IDs into batches of 100 or fewer. |
| Duplicate Tweet IDs | Deduplicate before sending. Keep source references separately. |
| Returned Tweet ID | Store the normalized row under the matching request ID. |
| Missing Tweet ID | Keep a missing row for targeted retry or review. |
| Low credit balance | Accept a smaller returned set and mark the missing IDs. |
| Zero affordable results | Stop on `402 insufficient_credits` and fund the account before retrying. |

## Query parameters

<ParamField query="ids" type="string" required>
  1 to 100 tweets, separated by commas. Each is a tweet ID or a tweet URL, such as `x.com/nasa/status/20`. A tweet named twice is read once.
</ParamField>

## Headers

<ParamField header="x-api-key" type="string">
  Full account API key. Session cookie and OAuth authentication are also supported.
</ParamField>

<ParamField header="Authorization" type="string">
  Send `Bearer xq_your_guest_key_here` for an active `paid_reads` guest key.
</ParamField>

## Response

### 200 OK

<ResponseField name="tweets" type="object[]">
  Array of tweets matching the requested IDs.
  **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="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 unavailable.</ResponseField>
  <ResponseField name="inReplyToUsername" type="string">Username being replied to. Omitted if unavailable.</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. 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 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>
  </ResponseField>

  <ResponseField name="media" type="object[]">
    Media attachments. Omitted when the tweet has no media.
    **Media object 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">Always `false` for batch requests.</ResponseField>
<ResponseField name="next_cursor" type="string">Always empty for batch requests.</ResponseField>

```json theme={null}
{
  "tweets": [
    {
      "id": "1893456789012345678",
      "text": "Hello world!",
      "createdAt": "2026-03-27T10:00:00.000Z",
      "likeCount": 42,
      "retweetCount": 5,
      "author": {
        "id": "9876543210",
        "username": "username",
        "name": "Xquik",
        "followers": 12000,
        "verified": true,
        "profilePicture": "https://pbs.twimg.com/profile_images/example.jpg"
      }
    }
  ],
  "has_next_page": false,
  "next_cursor": ""
}
```

### 400 Missing IDs

```json theme={null}
{ "error": "missing_ids", "message": "ids parameter required" }
```

### 400 Too many IDs

```json theme={null}
{ "error": "too_many_ids", "message": "Max 100 IDs per request" }
```

### 400 Invalid tweet ID

```json theme={null}
{
  "error": "invalid_tweet_id",
  "message": "Send ids as 1 to 100 tweet IDs or tweet links, separated by commas."
}
```

An entry of `ids` names no tweet. Send each entry as a tweet ID or a tweet URL. The request costs nothing.

### 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" }
```

### 402 Payment required

Full account keys can receive `no_subscription`, `subscription_inactive`, `no_credits`, or `insufficient_credits` with account payment options. Guest keys receive only the guest top-up action.

The failed request creates no checkout. Ask the user to confirm before calling any checkout or top-up route.

### 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.** [Batch Users](/api-reference/x/batch-users) · [Get Tweet](/api-reference/x/get-tweet)
</Note>

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


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