Skip to main content
Use this workflow when a support, community, research, or giveaway system needs tweet replies as rows. Xquik can scrape tweet replies with reply_extractor, estimate the cost first, run the extraction job, paginate JSON results, then export CSV, JSON, or XLSX for analysts, moderators, CRMs, warehouses, queues, or AI agents.

When to use this workflow

Spreadsheet export

Use reply_extractor plus CSV or XLSX export when analysts need reply rows.

App ingestion

Use reply_extractor plus paginated JSON results for queues, CRMs, or warehouses.

Moderation queue

Use the direct replies API plus JSON Lines rows before storing, hiding, labeling, or routing replies.

Latest page

Use GET /x/tweets/{id}/replies when you only need the newest reply page.

Cost control

Estimate the target tweet first, then set resultsLimit on create requests to cap the run.

Data you get

Reply exports include base user fields, reply tweet fields, engagement counts, and metadata when available.

Reply author

User ID, username, display name, follower count, verified state, and profile image.

Reply tweet

Tweet ID, tweet text, and tweet created time.

Engagement

Likes, reposts, replies, quotes, views, and bookmarks.

Metadata

Language, source app, and conversation ID.
For extraction jobs, GET /extractions/{id} returns job, results, hasMore, and nextCursor. Each reply row includes xUserId, xUsername, xDisplayName, tweetId, tweetText, tweetCreatedAt, createdAt, and optional enrichmentData fields such as likeCount, replyCount, repostCount, quoteCount, viewCount, bookmarkCount, conversationId, lang, and source.

End-to-end reply export handoff

Store one checkpoint that carries the target tweet through estimate, job creation, JSON pagination, and file export:
{
  "workflow": "reply_export",
  "request": {
    "toolType": "reply_extractor",
    "targetTweetId": "1893704267862470862",
    "resultsLimit": 500
  },
  "estimate": {
    "estimatedResults": 1200,
    "creditsRequired": "1200",
    "creditsAvailable": "77000",
    "allowed": true,
    "source": "replyCount"
  },
  "create_receipt": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "toolType": "reply_extractor",
    "status": "running",
    "poll_path": "/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  },
  "json_pages": {
    "limit": 1000,
    "page_cursor": null,
    "next_cursor": "990200",
    "has_more": true
  },
  "export_paths": {
    "csv": "/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/export?format=csv",
    "json": "/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/export?format=json",
    "xlsx": "/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/export?format=xlsx"
  },
  "normalized_row": {
    "parent_tweet_id": "1893704267862470862",
    "reply_tweet_id": "1893710452812718080",
    "reply_text": "Thanks for the update.",
    "reply_created_at": "2026-05-01T10:05:00.000Z",
    "reply_author_id": "44196397",
    "reply_author_username": "xquikcom",
    "conversation_id": "1893704267862470862",
    "export_format": "csv"
  },
  "handoff_state": "poll_until_completed_then_export"
}

Estimate checkpoint

Keep estimatedResults, creditsRequired, creditsAvailable, allowed, and source with targetTweetId; source is replyCount when the tweet count lookup succeeds.

Job checkpoint

Store the returned job id, status, and poll_path; do not expect reply rows in the create response.

Cursor checkpoint

Store page_cursor, next_cursor, and has_more for JSON page loops, then pass nextCursor back as after.

Export checkpoint

Store the chosen CSV, JSON, or XLSX export_paths and the normalized reply fields sent downstream.

Step 1: Estimate replies and credits

Call POST /extractions/estimate before scraping. reply_extractor requires targetTweetId. The estimate uses the tweet’s current replyCount, even when you plan to cap the created job.
curl -X POST https://xquik.com/api/v1/extractions/estimate \
  -H "x-api-key: xq_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "toolType": "reply_extractor",
    "targetTweetId": "1893704267862470862"
  }' | jq
The estimate returns allowed, estimatedResults, creditsRequired, creditsAvailable, and source. For reply scraping, source is replyCount when the tweet count lookup succeeds. Set resultsLimit on the create request when you want a smaller sample or hard run cap; final rows and credits can be lower than the estimate.

Step 2: Run the reply extraction

Create the job with the same toolType, targetTweetId, and optional resultsLimit.
curl -X POST https://xquik.com/api/v1/extractions \
  -H "x-api-key: xq_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "toolType": "reply_extractor",
    "targetTweetId": "1893704267862470862",
    "resultsLimit": 500
  }' | jq
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "toolType": "reply_extractor",
  "status": "running"
}
Store the creation response as a local handoff before polling:
{
  "job": "reply_extraction",
  "reply_extraction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "target_tweet_id": "1893704267862470862",
  "results_limit": 500,
  "status": "running",
  "handoff_created_at": "2026-05-16T03:07:00.000Z"
}
Poll by reply_extraction_id; keep target_tweet_id and results_limit with the audit record. Do not wait for totalResults or createdAt in the create response; those fields arrive from GET /extractions/{id}.

Step 3: Poll job status

Poll GET /extractions/{id} until the job is completed or failed.
curl https://xquik.com/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "x-api-key: xq_YOUR_KEY_HERE" | jq
Use the paginated response when your app wants JSON rows instead of a file download. Use limit up to 1,000 and pass nextCursor as after until hasMore is false.

Step 4: Export CSV, JSON, or XLSX

Exports are free after the extraction job exists. Save xquik-replies.jsonl for queue replay or warehouse loads, xquik-replies.json for app ingestion, xquik-replies.csv for CRM import, and xquik-replies.xlsx for analyst handoff.
curl -X GET "https://xquik.com/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/export?format=csv" \
  -H "x-api-key: xq_YOUR_KEY_HERE" \
  -o xquik-replies.csv
curl -X GET "https://xquik.com/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/export?format=xlsx" \
  -H "x-api-key: xq_YOUR_KEY_HERE" \
  -o xquik-replies.xlsx
curl -X GET "https://xquik.com/api/v1/extractions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/export?format=json" \
  -H "x-api-key: xq_YOUR_KEY_HERE" \
  -o xquik-replies.json

Saved export JSON Lines handoff

Use this after format=json when a warehouse, queue, CRM, or AI agent needs one reply per line with stable field names.
jq -c --arg extraction_id "a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  '.[] | {
    job: "reply_export",
    extraction_id: $extraction_id,
    reply_tweet_id: .tweetId,
    reply_text: .tweetText,
    reply_author_id: .xUserId,
    reply_author_username: .xUsername,
    created_at: .tweetCreatedAt,
    conversation_id: .conversationId,
    like_count: .likeCount,
    reply_count: .replyCount,
    quote_count: .quoteCount,
    view_count: .viewCount,
    handoff_format: "jsonl"
  }' xquik-replies.json > xquik-replies.jsonl

Direct replies API

Use GET /x/tweets/{id}/replies when you need a paginated API response instead of a stored extraction job.
curl "https://xquik.com/api/v1/x/tweets/1893704267862470862/replies" \
  -H "x-api-key: xq_YOUR_KEY_HERE" | jq
The direct replies API returns tweets, has_next_page, and next_cursor. Pass next_cursor back as cursor to fetch the next page. It costs 1 credit per tweet returned. Use sinceTime and untilTime as Unix timestamps in seconds when a moderation queue, CRM sync, or agent only needs replies from a specific window.
curl -G https://xquik.com/api/v1/x/tweets/1893704267862470862/replies \
  --data-urlencode "sinceTime=1777392000" \
  --data-urlencode "untilTime=1777478400" \
  -H "x-api-key: xq_YOUR_KEY_HERE" | jq

Copy-ready workflow: replies to moderation queue

Use this direct API workflow when you need the latest reply pages as JSON Lines before deciding what to store, hide, label, or route. Each row maps the API response into stable downstream fields: handoff_source, parent_tweet_id, reply_tweet_id, reply_text, reply_author_id, reply_author_username, reply_author_name, reply_author_followers, reply_author_verified, reply_author_profile_picture, created_at, in_reply_to_id, conversation_id, engagement counts, bookmark_count, is_note_tweet, tweet_source, media_urls, and cursor fields.
const apiKey = process.env.XQUIK_API_KEY;
const tweetId = "1893704267862470862";
let cursor;
let pageIndex = 0;

do {
  const url = new URL(
    `https://xquik.com/api/v1/x/tweets/${tweetId}/replies`,
  );
  if (cursor) {
    url.searchParams.set("cursor", cursor);
  }

  const response = await fetch(url, {
    headers: { "x-api-key": apiKey },
  });

  if (response.status === 429) {
    throw new Error("Rate limited. Retry after the response header delay.");
  }
  if (!response.ok) {
    const body = await response.json();
    throw new Error(`${response.status} ${body.error}`);
  }

  const page = await response.json();
  for (const tweet of page.tweets ?? []) {
    const row = {
      handoff_source: "xquik.replies.direct",
      parent_tweet_id: tweetId,
      reply_tweet_id: tweet.id,
      reply_text: tweet.text,
      reply_author_id: tweet.author?.id ?? null,
      reply_author_username: tweet.author?.username ?? null,
      reply_author_name: tweet.author?.name ?? null,
      reply_author_followers: tweet.author?.followers ?? null,
      reply_author_verified: tweet.author?.verified ?? null,
      reply_author_profile_picture: tweet.author?.profilePicture ?? null,
      created_at: tweet.createdAt ?? null,
      in_reply_to_id: tweet.inReplyToId ?? null,
      conversation_id: tweet.conversationId ?? null,
      like_count: tweet.likeCount ?? 0,
      reply_count: tweet.replyCount ?? 0,
      retweet_count: tweet.retweetCount ?? 0,
      quote_count: tweet.quoteCount ?? 0,
      view_count: tweet.viewCount ?? 0,
      bookmark_count: tweet.bookmarkCount ?? 0,
      is_note_tweet: tweet.isNoteTweet ?? false,
      tweet_source: tweet.source ?? null,
      media_urls: (tweet.media ?? [])
        .map((item) => item.mediaUrl)
        .filter(Boolean),
      page_index: pageIndex,
      page_cursor: cursor ?? "",
      next_cursor: page.next_cursor,
      has_next_page: page.has_next_page,
    };
    process.stdout.write(JSON.stringify(row) + "\n");
  }

  cursor = page.has_next_page ? page.next_cursor : undefined;
  pageIndex += 1;
} while (cursor);
Use the extraction workflow instead when the job needs a durable export file, a cost estimate before scraping, or a hard resultsLimit.

Cost and failure handling

Estimate is free. Creating a reply_extractor job costs 1 credit per result extracted. Direct GET /x/tweets/{id}/replies calls cost 1 credit per tweet returned, and the returned page size can be reduced by available credits. Exports are free after the extraction job exists. Handle common errors before retrying:

400 invalid tweet ID

Status 400. Error invalid_tweet_id. Use a numeric tweet ID.

401 unauthenticated

Status 401. Error unauthenticated. Send a valid x-api-key.

402 billing or credits

Status 402. Errors no_subscription, subscription_inactive, no_credits, or insufficient_credits. Subscribe, add credits, or lower resultsLimit.

429 rate limit

Status 429. Error rate_limit_exceeded. Wait for retryAfter or the Retry-After header.

424 or 502 upstream unavailable

Status 424 or 502. Error x_api_unavailable. Retry with exponential backoff.

Handoff checklist

Spreadsheet

Export format=csv to xquik-replies.csv or format=xlsx to xquik-replies.xlsx.

App ingestion

Export format=json to xquik-replies.json, convert it to xquik-replies.jsonl, or paginate GET /extractions/{id}.

Cost control

Set resultsLimit on create calls when you need a smaller run.

Real-time replies

Create an account or keyword monitor with tweet.reply events.
Last modified on May 23, 2026