Use this file to discover all available pages before exploring further.
Use the Kotlin SDK for generated JVM models, Kotlin nullable values, builders, sync calls, async calls, typed exceptions, retries, and file upload helpers. It is useful when a JVM service, Ktor worker, Spring job, queue consumer, or cron task needs to search tweets, scrape tweets to JSON Lines, CSV, or XLSX, export followers, monitor tweets, post media tweets, upload media, send direct messages, or hand X API data to analytics and CRM systems.
Maven Central publication is pending. Build from source until the com.x_twitter_scraper.api:x-twitter-scraper-kotlin artifact resolves in Maven Central.
Workflow: Search Tweets to JSON Lines, CSV, or XLSX
Use this workflow when a Kotlin service, scheduled worker, queue consumer, or agent needs tweet search results in a durable handoff file for a data lake, CRM enrichment step, analyst CSV export, XLSX workbook, or downstream processor.client.x().tweets().search calls GET /x/tweets/search. Build TweetSearchParams with the same query parameters the REST API accepts: .q(), .limit(), .cursor(), .sinceTime(), .untilTime(), and .queryType().
import com.fasterxml.jackson.databind.ObjectMapperimport com.x_twitter_scraper.api.client.XTwitterScraperClientimport com.x_twitter_scraper.api.client.okhttp.XTwitterScraperOkHttpClientimport com.x_twitter_scraper.api.models.PaginatedTweetsimport com.x_twitter_scraper.api.models.SearchTweetimport com.x_twitter_scraper.api.models.x.tweets.TweetSearchParamsimport com.x_twitter_scraper.api.models.x.tweets.TweetSearchParams.QueryTypeimport java.io.BufferedWriterimport java.nio.charset.StandardCharsetsimport java.nio.file.Filesimport java.nio.file.Pathsval client: XTwitterScraperClient = XTwitterScraperOkHttpClient.fromEnv()val objectMapper = ObjectMapper()val query = "from:xquikcom webhook OR SDK"var cursor: String? = nullvar pageIndex = 0val headers = listOf( "source", "query", "tweet_id", "text", "author_id", "author_username", "author_name", "created_at", "like_count", "reply_count", "retweet_count", "quote_count", "view_count", "bookmark_count", "is_note_tweet", "page_index", "page_cursor", "next_cursor", "has_next_page",)Files.newBufferedWriter(Paths.get("xquik-tweet-search.jsonl"), StandardCharsets.UTF_8).use { jsonlWriter -> Files.newBufferedWriter(Paths.get("xquik-tweet-search.csv"), StandardCharsets.UTF_8).use { csvWriter -> writeCsvRow(csvWriter, headers) do { val pageCursor = cursor val builder = TweetSearchParams.builder() .q(query) .queryType(QueryType.LATEST) if (!cursor.isNullOrEmpty()) { builder.cursor(cursor) } val page: PaginatedTweets = client.x().tweets().search(builder.build()) for (tweet: SearchTweet in page.tweets()) { val author = tweet.author() val row = linkedMapOf<String, Any?>( "source" to "xquik.kotlin.search", "query" to query, "tweet_id" to tweet.id(), "text" to tweet.text(), "author_id" to author?.id(), "author_username" to author?.username(), "author_name" to author?.name(), "created_at" to tweet.createdAt(), "like_count" to (tweet.likeCount() ?: 0L), "reply_count" to (tweet.replyCount() ?: 0L), "retweet_count" to (tweet.retweetCount() ?: 0L), "quote_count" to (tweet.quoteCount() ?: 0L), "view_count" to (tweet.viewCount() ?: 0L), "bookmark_count" to (tweet.bookmarkCount() ?: 0L), "is_note_tweet" to (tweet.isNoteTweet() ?: false), "page_index" to pageIndex, "page_cursor" to pageCursor, "next_cursor" to if (page.hasNextPage()) page.nextCursor() else null, "has_next_page" to page.hasNextPage(), ) jsonlWriter.write(objectMapper.writeValueAsString(row)) jsonlWriter.newLine() writeCsvRow(csvWriter, headers.map { header -> row[header] }) } pageIndex++ cursor = if (page.hasNextPage()) page.nextCursor() else null } while (!cursor.isNullOrEmpty()) }}fun writeCsvRow(writer: BufferedWriter, values: List<Any?>) { writer.write( values.joinToString(",") { value -> val cell = value?.toString() ?: "" "\"" + cell.replace("\"", "\"\"") + "\"" } ) writer.newLine()}
Kotlin builder method .q() maps to REST q. Use it for an X search query such as from:username, a keyword, hashtag, or boolean operator query.
.limit()
Kotlin builder method .limit() maps to REST limit. Use it for a bounded request from 1 to 200. Omit it for cursor loops.
.cursor()
Kotlin builder method .cursor() maps to REST cursor. Pass the opaque cursor from page.nextCursor() to request the next page.
.sinceTime()
Kotlin builder method .sinceTime() maps to REST sinceTime. Use it as the ISO 8601 lower bound for tweet creation time.
.untilTime()
Kotlin builder method .untilTime() maps to REST untilTime. Use it as the ISO 8601 upper bound for tweet creation time.
.queryType()
Kotlin builder method .queryType() maps to REST queryType. Use QueryType.LATEST for chronological search or QueryType.TOP for engagement-ranked search.
client.x().tweets().search returns PaginatedTweets. Use page.tweets() for the tweet list, page.hasNextPage() to decide whether another page exists, and page.nextCursor() as the checkpoint for the next request.Each SearchTweet includes generated accessors such as id(), text(), author(), createdAt(), likeCount(), replyCount(), retweetCount(), quoteCount(), viewCount(), bookmarkCount(), and isNoteTweet() when available. Project page.tweets() into JSON Lines and CSV rows with tweet_id, author_username, engagement counts, page_index, page_cursor, next_cursor, and has_next_page so workers can resume safely or load the same records into XLSX, CRM, warehouse, or agent workflows.Tweet search costs 1 credit per tweet returned. If remaining credits cannot cover a bounded .limit() request, the API can return fewer tweets; if 0 paid results are affordable, it returns 402 insufficient_credits. The SDK supports withOptions() for retry and request settings. For longer workers, set an explicit retry limit and persist page.nextCursor() after each page.
Use this workflow when a Kotlin worker needs an owned follower list for CRM import, warehouse loading, account scoring, analyst CSV, XLSX workbook delivery, or a resumable JSON handoff.client.extractions().estimateCost maps to POST /extractions/estimate, client.extractions().run maps to POST /extractions, client.extractions().retrieve maps to GET /extractions/{id}, and client.extractions().exportResults maps to GET /extractions/{id}/export. For follower exports, follower_explorer requires targetUsername.
Persist job.id(), targetUsername, estimate.estimatedResults(), and estimate.source() before polling so a queue retry can resume without rerunning the extraction. client.extractions().retrieve returns results(), hasMore(), and nextCursor(); pass nextCursor() back as after to page through large follower lists. Map exported User ID or row xUserId as the CRM unique key. Use xquik-followers.jsonl for queue replay or warehouse loads, xquik-followers.json for app ingestion, xquik-followers.csv for CRM import, and xquik-followers.xlsx for analyst handoff. Cost: 1 credit per follower extracted or returned. Exports are free after the extraction job exists.
Use this workflow when a Kotlin worker needs every reply to a campaign, support thread, launch post, or incident update as a durable file. reply_extractor requires targetTweetId; estimate first, run the job, poll retrieve, then export the completed job as CSV, JSON, or XLSX.client.extractions().estimateCost maps to POST /extractions/estimate, client.extractions().run maps to POST /extractions, client.extractions().retrieve maps to GET /extractions/{id}, and client.extractions().exportResults maps to GET /extractions/{id}/export.
Persist job.id() before polling so a queue retry can resume with client.extractions().retrieve(job.id()) and repeat client.extractions().exportResults(...) after completion. Keep page.nextCursor() as the checkpoint when you stream replies to JSON Lines. Use 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. Cost: 1 credit per reply extracted or returned.
Use this workflow when a Kotlin service needs to publish a media tweet, reply with media, or send a direct message with an uploaded local file. client.x().tweets().create maps to POST /x/tweets; pass public media URLs through .addMedia() or .media(). Send up to 4 image URLs or exactly 1 MP4 video URL up to 100 MB, and do not mix video with other media. For replies, set .replyToTweetId() to the parent tweet ID. client.x().media().upload maps to POST /x/media; use media.mediaId() only for the one-item DM .addMediaId() handoff.
The generated Kotlin response model covers confirmed tweetId responses. Use client.x().tweets().withRawResponse().create(...) when a write worker must branch on the REST 202 x_write_unconfirmed response, store writeActionId and chargedCredits, and poll Get Write Action Status before sending another write. Store tweetHandoff, replyHandoff, and dmHandoff on the CMS, support ticket, queue job, CRM note, or agent state before scheduling follow-up work. Text-only tweet and reply writes cost 10 credits. Tweet media adds 2 credits per started MB across attached files. Uploading media costs 10 credits, and sending the DM costs 10 credits. Do not pass uploaded media.mediaId() values to client.x().tweets().create; that method uses public media URLs through media.